From 262ae769b0243bb57a5297f9f15d1d3fbfe4fc0c Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Fri, 14 May 2021 15:29:18 -0700 Subject: [PATCH 01/22] Reapply All Apps icons on download complete. Fixes: 187853323 Test: manual Change-Id: I800a13a3665997f323f23667c8128c8034a7b4d3 --- src/com/android/launcher3/allapps/AllAppsStore.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 355ccadeba..2443b8360d 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -17,6 +17,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR; import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; import android.view.View; import android.view.ViewGroup; @@ -157,11 +158,17 @@ public class AllAppsStore { * If this app is installed and supports incremental downloads, the progress bar will be updated * the app's total download progress. Otherwise, the progress bar will be updated to the app's * installation progress. + * + * If this app is fully downloaded, the app icon will be reapplied. */ public void updateProgressBar(AppInfo app) { updateAllIcons((child) -> { if (child.getTag() == app) { - child.applyProgressLevel(); + if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) { + child.applyFromApplicationInfo(app); + } else { + child.applyProgressLevel(); + } } }); } From 66034efaaed742c798016bd55a5d9292d87fdeff Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Tue, 18 May 2021 18:52:13 +0100 Subject: [PATCH 02/22] Clear widgets search bar focus when the user taps outside the search bar Test: Focus widgets search bar. Then tap outside the search bar and observe the keyboard is being dismissed. Bug: 188540267 Change-Id: Ica1b5b804ed97dd0b1cd8d41351eddcf5249ded0 --- .../widget/picker/WidgetsListAdapterTest.java | 2 +- ...WidgetsListHeaderViewHolderBinderTest.java | 3 +-- ...sListSearchHeaderViewHolderBinderTest.java | 3 +-- .../WidgetsListTableViewHolderBinderTest.java | 3 +-- .../widget/picker/WidgetsFullSheet.java | 19 ++++++------- .../widget/picker/WidgetsListAdapter.java | 9 +------ .../search/LauncherWidgetsSearchBar.java | 5 ++++ .../picker/search/WidgetsSearchBar.java | 3 +++ .../search/WidgetsSearchBarUIHelper.java | 27 ------------------- 9 files changed, 21 insertions(+), 53 deletions(-) delete mode 100644 src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java index 6b5678cb8a..e1214ff39f 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java @@ -82,7 +82,7 @@ public final class WidgetsListAdapterTest { mTestProfile.numColumns = 5; mUserHandle = Process.myUserHandle(); mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache, - mIconCache, null, null, null); + mIconCache, null, null); mAdapter.registerAdapterDataObserver(mListener); doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0)) diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java index 12a092dafe..84a03d572c 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java @@ -105,8 +105,7 @@ public final class WidgetsListHeaderViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListHeaderViewHolderBinder( LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter); } diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java index e09034132c..075c58db6b 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java @@ -105,8 +105,7 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder( LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter); } diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java index 0935d1cfc4..0c6e71711d 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java @@ -111,8 +111,7 @@ public final class WidgetsListTableViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListTableViewHolderBinder( mContext, LayoutInflater.from(mTestActivity), diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index e44acc363b..121df961df 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -61,7 +61,6 @@ import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListene import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; -import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper; import com.android.launcher3.widget.util.WidgetsTableUtils; import com.android.launcher3.workprofile.PersonalWorkPagedView; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; @@ -76,8 +75,7 @@ import java.util.stream.IntStream; */ public class WidgetsFullSheet extends BaseWidgetSheet implements Insettable, ProviderChangedListener, OnActivePageChangedListener, - WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener, - WidgetsSearchBarUIHelper { + WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener { private static final String TAG = WidgetsFullSheet.class.getSimpleName(); private static final long DEFAULT_OPEN_DURATION = 267; @@ -535,6 +533,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet } else if (getPopupContainer().isEventOverView(mContent, ev)) { mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer()); } + + if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused() + && !getPopupContainer().isEventOverView( + mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) { + mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus(); + } } return super.onControllerInterceptTouchEvent(ev); } @@ -609,11 +613,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet getWindowInsetsController().hide(WindowInsets.Type.ime()); } - @Override - public void clearSearchBarFocus() { - mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus(); - } - private void showEducationTipOnView(View view) { mActivityContext.getSharedPrefs().edit() .putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply(); @@ -681,9 +680,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet apps.getWidgetCache(), apps.getIconCache(), /* iconClickListener= */ WidgetsFullSheet.this, - /* iconLongClickListener= */ WidgetsFullSheet.this, - /* WidgetsSearchBarUIHelper= */ - mAdapterType == SEARCH ? WidgetsFullSheet.this : null); + /* iconLongClickListener= */ WidgetsFullSheet.this); mWidgetsListAdapter.setHasStableIds(true); switch (mAdapterType) { case PRIMARY: diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 0504e74695..c0981abbd9 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -41,7 +41,6 @@ import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry; -import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper; import java.util.ArrayList; import java.util.Arrays; @@ -70,7 +69,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header; private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header; - @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper; private final WidgetsDiffReporter mDiffReporter; private final SparseArray mViewHolderBinders = new SparseArray<>(); private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; @@ -90,9 +88,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, - OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) { - mSearchBarUIHelper = searchBarUIHelper; + OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { mDiffReporter = new WidgetsDiffReporter(iconCache, this); mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context, layoutInflater, iconClickListener, iconLongClickListener, @@ -243,9 +239,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Override public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) { - if (mSearchBarUIHelper != null) { - mSearchBarUIHelper.clearSearchBarFocus(); - } if (showWidgets) { mWidgetsContentVisiblePackageUserKey = packageUserKey; updateVisibleEntries(); diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java index 42f1bb2d21..65937b644a 100644 --- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java @@ -75,6 +75,11 @@ public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSea mController.onDestroy(); } + @Override + public boolean isSearchBarFocused() { + return mEditText.isFocused(); + } + @Override public void clearSearchBarFocus() { mController.clearFocus(); diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java index 0ac47ce5f2..44a5e80f5e 100644 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java @@ -32,6 +32,9 @@ public interface WidgetsSearchBar { */ void reset(); + /** Returns {@code true} if the search bar is in focus. */ + boolean isSearchBarFocused(); + /** * Clears focus from search bar. */ diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java deleted file mode 100644 index edfdc655ed..0000000000 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.widget.picker.search; - -/** - * UI helper for {@link WidgetsSearchBar}. - */ -public interface WidgetsSearchBarUIHelper { - /** - * Clears focus from the search bar. - */ - void clearSearchBarFocus(); -} From 1a55d1ff37b7237fc2bbd8a848470eda5e9bbbed Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Tue, 18 May 2021 17:18:07 +0100 Subject: [PATCH 03/22] Widgets: Only reset expanded item & scroll to top if the user switch tab Test: Open widgets picker, then adb install an app that has widget. Observe the newly installed app widget is shown and the widgets picker doesn't scroll itself back to top. Bug: 188425375 Change-Id: I7ac29f08b06d528ef64e902e678a67e56971c681 --- .../launcher3/widget/picker/WidgetsFullSheet.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index e44acc363b..bfdddebcf9 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -130,6 +130,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet private final int mTabsHeight; private final int mWidgetCellHorizontalPadding; + @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView; @Nullable private PersonalWorkPagedView mViewPager; private boolean mIsInSearchMode; private int mMaxSpansPerRow = 4; @@ -222,13 +223,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet updateRecyclerViewVisibility(currentAdapterHolder); attachScrollbarToRecyclerView(currentRecyclerView); - resetExpandedHeaders(); } private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) { recyclerView.bindFastScrollbar(); - mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView); - reset(); + if (mCurrentWidgetsRecyclerView != recyclerView) { + // Only reset the scroll position & expanded apps if the currently shown recycler view + // has been updated. + reset(); + resetExpandedHeaders(); + mCurrentWidgetsRecyclerView = recyclerView; + mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView); + } } private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { @@ -413,7 +419,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mIsInSearchMode) return; setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true); attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView); - resetExpandedHeaders(); } @Override From 2f2cbce8186a8a15f55b522f36a0ce1ff740f848 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Tue, 18 May 2021 20:54:18 +0100 Subject: [PATCH 04/22] Reset visible entries when the app tile of the same app no longer match Test: Switch language and then go to full widgets picker. Expand apps. Expanded entry is shown underneath the expanded app. Bug: 187529983 Change-Id: Ib438cf01656d3a177309e7f22796d0fa0c11881f --- .../widget/picker/WidgetsListAdapter.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 0504e74695..8345a0eaec 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; import com.android.launcher3.util.LabelComparator; import com.android.launcher3.util.PackageUserKey; @@ -47,6 +48,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -153,6 +155,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void setWidgets(List tempEntries) { mAllEntries = tempEntries.stream().sorted(mRowComparator) .collect(Collectors.toList()); + if (shouldClearVisibleEntries()) { + mVisibleEntries.clear(); + } updateVisibleEntries(); } @@ -272,6 +277,30 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mWidgetsListTableViewHolderBinder.setMaxSpansPerRow(maxHorizontalSpans); } + /** + * Returns {@code true} if there is a change in {@link #mAllEntries} that results in an + * invalidation of {@link #mVisibleEntries}. e.g. there is change in the device language. + */ + private boolean shouldClearVisibleEntries() { + Map packagesInfo = + mAllEntries.stream() + .filter(entry -> entry instanceof WidgetsListHeaderEntry) + .map(entry -> entry.mPkgItem) + .collect(Collectors.toMap( + entry -> new PackageUserKey(entry.packageName, entry.user), + entry -> entry)); + for (WidgetsListBaseEntry visibleEntry: mVisibleEntries) { + PackageUserKey key = new PackageUserKey(visibleEntry.mPkgItem.packageName, + visibleEntry.mPkgItem.user); + PackageItemInfo packageItemInfo = packagesInfo.get(key); + if (packageItemInfo != null + && !visibleEntry.mPkgItem.title.equals(packageItemInfo.title)) { + return true; + } + } + return false; + } + /** Comparator for sorting WidgetListRowEntry based on package title. */ public static class WidgetListBaseRowEntryComparator implements Comparator { From 427c143ef61704c072ccd876192b3b5dd711e062 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Wed, 19 May 2021 14:30:47 +0100 Subject: [PATCH 05/22] Fix WidgetsSearchBarControllerTest Test: Run WidgetsSearchBarControllerTest Bug: 188650001 Change-Id: I687d602f8d857061f71a8fba86c30ea3017065c1 --- .../launcher3/testing/TestActivity.java | 7 +++- .../WidgetsSearchBarControllerTest.java | 32 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java index dbf4b3e6c0..17d0ac1a9f 100644 --- a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java +++ b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java @@ -27,7 +27,12 @@ public class TestActivity extends BaseActivity implements ActivityContext { @Override public BaseDragLayer getDragLayer() { - return null; + return new BaseDragLayer(this, /* attrs= */ null, /* alphaChannelCount= */ 1) { + @Override + public void recreateControllers() { + // Do nothing. + } + }; } @Override diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java index 4e6f17c384..a057a84d06 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java @@ -19,24 +19,27 @@ package com.android.launcher3.widget.picker.search; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.content.Context; import android.view.View; import android.widget.ImageButton; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.search.SearchAlgorithm; +import com.android.launcher3.testing.TestActivity; import com.android.launcher3.widget.model.WidgetsListBaseEntry; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; import java.util.ArrayList; @@ -44,7 +47,9 @@ import java.util.ArrayList; public class WidgetsSearchBarControllerTest { private WidgetsSearchBarController mController; - private Context mContext; + // TODO: Replace ActivityController with ActivityScenario, which is the recommended way for + // activity testing. + private ActivityController mActivityController; private ExtendedEditText mEditText; private ImageButton mCancelButton; @Mock @@ -55,13 +60,20 @@ public class WidgetsSearchBarControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mEditText = new ExtendedEditText(mContext); - mCancelButton = new ImageButton(mContext); + mActivityController = Robolectric.buildActivity(TestActivity.class); + TestActivity testActivity = mActivityController.setup().get(); + + mEditText = new ExtendedEditText(testActivity); + mCancelButton = new ImageButton(testActivity); mController = new WidgetsSearchBarController( mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener); } + @After + public void tearDown() { + mActivityController.destroy(); + } + @Test public void onSearchResult_shouldInformSearchModeListener() { ArrayList entries = new ArrayList<>(); @@ -119,14 +131,18 @@ public class WidgetsSearchBarControllerTest { public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() { mCancelButton.performClick(); - verify(mSearchModeListener).exitSearchMode(); + // 1 time explicitly from the cancel button on click listener. + // Another from the setText("") the cancel button on click listener causing afterTextChange. + verify(mSearchModeListener, times(2)).exitSearchMode(); } @Test public void cancelSearch_shouldCancelSearch() { mCancelButton.performClick(); - verify(mSearchAlgorithm).cancel(true); + // 1 time explicitly from the cancel button on click listener. + // Another from the setText("") the cancel button on click listener causing afterTextChange. + verify(mSearchAlgorithm, times(2)).cancel(true); verifyNoMoreInteractions(mSearchAlgorithm); } From f735c738ea01ff894a8fea329db9464f53dc2a54 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Wed, 19 May 2021 21:34:06 +0100 Subject: [PATCH 06/22] Remove work profile badge from widget previews Test: Check work profile badge no longer shown on work widget in the full and bottom widgets pickers. Bug: 177675123 Change-Id: I0e5e32e18df832c1919749404094d68805c30556 --- res/layout/widget_cell_content.xml | 8 --- .../launcher3/WidgetPreviewLoader.java | 49 ------------------- .../android/launcher3/widget/WidgetCell.java | 14 ------ 3 files changed, 71 deletions(-) diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml index 0f6fc6cdb7..b27b50560e 100644 --- a/res/layout/widget_cell_content.xml +++ b/res/layout/widget_cell_content.xml @@ -33,14 +33,6 @@ android:layout_height="match_parent" android:importantForAccessibility="no" android:layout_gravity="fill"/> - - diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 75d25d7aa3..8b7a750cc7 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -21,9 +21,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; @@ -35,9 +33,7 @@ import android.util.LongSparseArray; import android.util.Pair; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; @@ -93,51 +89,6 @@ public class WidgetPreviewLoader { mDb = new CacheDb(context); } - /** - * Returns a drawable that can be used as a badge for the user or null. - */ - @UiThread - public Drawable getBadgeForUser(UserHandle user, int badgeSize) { - if (mMyUser.equals(user)) { - return null; - } - - Bitmap badgeBitmap = getUserBadge(user, badgeSize); - FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); - d.setFilterBitmap(true); - d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); - return d; - } - - private Bitmap getUserBadge(UserHandle user, int badgeSize) { - synchronized (mUserBadges) { - Bitmap badgeBitmap = mUserBadges.get(user); - if (badgeBitmap != null) { - return badgeBitmap; - } - - final Resources res = mContext.getResources(); - badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - - Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity( - new BitmapDrawable(res, badgeBitmap), user, - new Rect(0, 0, badgeSize, badgeSize), - 0); - if (drawable instanceof BitmapDrawable) { - badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); - } else { - badgeBitmap.eraseColor(Color.TRANSPARENT); - Canvas c = new Canvas(badgeBitmap); - drawable.setBounds(0, 0, badgeSize, badgeSize); - drawable.draw(c); - c.setBitmap(null); - } - - mUserBadges.put(user, badgeBitmap); - return badgeBitmap; - } - } - /** * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be * called on UI thread diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 3fcd3f7b03..f7993dc408 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -33,7 +33,6 @@ import android.view.View.OnLayoutChangeListener; import android.view.ViewPropertyAnimator; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -45,7 +44,6 @@ import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.RoundDrawableWrapper; import com.android.launcher3.model.WidgetItem; @@ -80,7 +78,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private FrameLayout mWidgetImageContainer; private WidgetImageView mWidgetImage; - private ImageView mWidgetBadge; private TextView mWidgetName; private TextView mWidgetDims; private TextView mWidgetDescription; @@ -136,7 +133,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mWidgetImageContainer = findViewById(R.id.widget_preview_container); mWidgetImage = findViewById(R.id.widget_preview); - mWidgetBadge = findViewById(R.id.widget_badge); mWidgetName = findViewById(R.id.widget_name); mWidgetDims = findViewById(R.id.widget_dims); mWidgetDescription = findViewById(R.id.widget_description); @@ -161,7 +157,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mWidgetImage.animate().cancel(); mWidgetImage.setDrawable(null); mWidgetImage.setVisibility(View.VISIBLE); - mWidgetBadge.setImageDrawable(null); mWidgetName.setText(null); mWidgetDims.setText(null); mWidgetDescription.setText(null); @@ -294,15 +289,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mAppWidgetHostViewPreview = null; } } - Drawable badge = mWidgetPreviewLoader.getBadgeForUser(mItem.user, - BaseIconFactory.getBadgeSizeForIconSize( - mActivity.getDeviceProfile().allAppsIconSizePx)); - if (badge == null) { - mWidgetBadge.setVisibility(View.GONE); - } else { - mWidgetBadge.setVisibility(View.VISIBLE); - mWidgetBadge.setImageDrawable(badge); - } if (mAnimatePreview) { mWidgetImageContainer.setAlpha(0f); ViewPropertyAnimator anim = mWidgetImageContainer.animate(); From cdbaea44865297a31a3c14c620c7e526c5765911 Mon Sep 17 00:00:00 2001 From: Jon Spivack Date: Mon, 17 May 2021 19:15:25 -0700 Subject: [PATCH 07/22] Disable NIU Actions on Work Profile apps If the currently selected Recent app belongs to a Work Profile, do not allow the user to invoke any of the overview actions. Instead, show the standard blocked by policy message (R.string.blocked_by_policy). Bug: 187491010 Test: Manual (Pixel 3A) Test: m -j RunLauncherGoGoogleRoboTests Change-Id: I552da9512ab19993eb4bb65fe9269d2aee9f0c4e --- .../src/com/android/quickstep/TaskOverlayFactoryGo.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java index 907767551c..117b8e6b92 100644 --- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java +++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.graphics.Matrix; import android.net.Uri; import android.os.SystemClock; +import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; @@ -96,7 +97,10 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { } getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); - boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot(); + // Disable Overview Actions for Work Profile apps + boolean isManagedProfileTask = + UserManager.get(mApplicationContext).isManagedProfile(task.key.userId); + boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot() && !isManagedProfileTask; getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task)); mTaskPackageName = task.key.getPackageName(); @@ -127,8 +131,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { /** * Creates and sends an Intent corresponding to the button that was clicked */ - @VisibleForTesting - public void sendNIUIntent(String actionType) { + private void sendNIUIntent(String actionType) { Intent intent = createNIUIntent(actionType); // Only add and send the image if the appropriate permissions are held if (mAssistPermissionsEnabled) { From 73d44f63b42ab42730e150604c781510b6903282 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Wed, 19 May 2021 23:47:23 +0100 Subject: [PATCH 08/22] Animate recommended widgets vertical transition when reset widgets picker scroll position Test: Set up work profile. Then open the widgets picker, scroll down from the personal tab, then switch tab. Observes a vertical transition animation of the search and recommendation container view. Bug: 188538428 Change-Id: I2b8523d1682a6312de3b5393c0bff782ae110b3c --- ...rchAndRecommendationsScrollController.java | 72 +++++++++++++------ .../widget/picker/WidgetsFullSheet.java | 2 +- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java index 34346ab672..6781824d74 100644 --- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java +++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.widget.picker; +import android.animation.ValueAnimator; import android.graphics.Point; import android.view.MotionEvent; import android.view.View; @@ -32,13 +33,14 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView; * vertical displacement upon scrolling. */ final class SearchAndRecommendationsScrollController implements - RecyclerViewFastScroller.OnFastScrollChangeListener { + RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener { private final boolean mHasWorkProfile; private final SearchAndRecommendationViewHolder mViewHolder; private final View mSearchAndRecommendationViewParent; private final WidgetsRecyclerView mPrimaryRecyclerView; private final WidgetsRecyclerView mSearchRecyclerView; private final int mTabsHeight; + private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0); private final Point mTempOffset = new Point(); // The following are only non null if mHasWorkProfile is true. @@ -47,8 +49,9 @@ final class SearchAndRecommendationsScrollController implements @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager; private WidgetsRecyclerView mCurrentRecyclerView; + private int mCurrentRecyclerViewScrollY = 0; - private OnContentChangeListener mOnContentChangeListener = () -> applyVerticalTransition(); + private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged(); /** * The vertical distance, in pixels, until the search is pinned at the top of the screen when @@ -89,23 +92,25 @@ final class SearchAndRecommendationsScrollController implements mPrimaryWorkTabsView = personalWorkTabsView; mPrimaryWorkViewPager = primaryWorkViewPager; mTabsHeight = tabsHeight; - setCurrentRecyclerView(mPrimaryRecyclerView); + setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false); + } + + public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { + setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true); } /** Sets the current active {@link WidgetsRecyclerView}. */ - public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { + private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView, + boolean animateReset) { + if (mCurrentRecyclerView == currentRecyclerView) { + return; + } if (mCurrentRecyclerView != null) { mCurrentRecyclerView.setOnContentChangeListener(null); } mCurrentRecyclerView = currentRecyclerView; mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener); - mViewHolder.mHeaderTitle.setTranslationY(0); - mViewHolder.mRecommendedWidgetsTable.setTranslationY(0); - mViewHolder.mSearchBarContainer.setTranslationY(0); - - if (mHasWorkProfile) { - mPrimaryWorkTabsView.setTranslationY(0); - } + reset(animateReset); } /** @@ -222,6 +227,12 @@ final class SearchAndRecommendationsScrollController implements @Override public void onScrollChanged() { + int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY(); + if (recyclerViewYOffset < 0) return; + mCurrentRecyclerViewScrollY = recyclerViewYOffset; + if (mAnimator.isStarted()) { + mAnimator.cancel(); + } applyVerticalTransition(); } @@ -230,34 +241,43 @@ final class SearchAndRecommendationsScrollController implements * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view. */ private void applyVerticalTransition() { - // Always use the recycler view offset because fast scroller offset has a different scale. - int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY(); - if (recyclerViewYOffset < 0) return; - if (mCollapsibleHeightForRecommendation > 0) { - int yDisplacement = Math.max(-recyclerViewYOffset, + int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY, -mCollapsibleHeightForRecommendation); mViewHolder.mHeaderTitle.setTranslationY(yDisplacement); mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement); } if (mCollapsibleHeightForSearch > 0) { - int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch); + int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY, + -mCollapsibleHeightForSearch); mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement); } if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) { - int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs); + int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY, + -mCollapsibleHeightForTabs); mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs); } } /** Resets any previous view translation. */ - public void reset() { - mViewHolder.mHeaderTitle.setTranslationY(0); - mViewHolder.mSearchBarContainer.setTranslationY(0); - if (mHasWorkProfile) { - mPrimaryWorkTabsView.setTranslationY(0); + public void reset(boolean animate) { + if (mCurrentRecyclerViewScrollY == 0) { + return; + } + if (mAnimator.isStarted()) { + mAnimator.cancel(); + } + + if (animate) { + mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0); + mAnimator.addUpdateListener(this); + mAnimator.setDuration(300); + mAnimator.start(); + } else { + mCurrentRecyclerViewScrollY = 0; + applyVerticalTransition(); } } @@ -308,6 +328,12 @@ final class SearchAndRecommendationsScrollController implements + marginLayoutParams.topMargin; } + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue(); + applyVerticalTransition(); + } + /** * A listener to be notified when there is a content change in the recycler view that may affect * the relative position of the search and recommendation container. diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index e44acc363b..9c7e404bf0 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -248,7 +248,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop(); } mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop(); - mSearchAndRecommendationsScrollController.reset(); + mSearchAndRecommendationsScrollController.reset(/* animate= */ true); } @VisibleForTesting From f833ecebfaf2e40b32b4d3d86f3eb95760ce42aa Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 18 May 2021 17:14:52 -0700 Subject: [PATCH 09/22] Notify when quickstep gesture starts Fixes: 182500083 Test: manual Change-Id: I1ab27b309c0dd2640add1aa83566acee20708a3a --- .../android/quickstep/AbsSwipeUpHandler.java | 1 + .../quickstep/RotationTouchHelper.java | 2 +- .../com/android/quickstep/SystemUiProxy.java | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index e0f430d6cf..005e9b5a7d 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -750,6 +750,7 @@ public abstract class AbsSwipeUpHandler, setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */); mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; + SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted(); } /** diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java index 070d725890..66929d0cdb 100644 --- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java +++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java @@ -369,7 +369,7 @@ public class RotationTouchHelper implements private void notifySysuiOfCurrentRotation(int rotation) { UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext) - .onQuickSwitchToNewTask(rotation)); + .notifyPrioritizedRotation(rotation)); } /** diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index acf999253f..7ef6a4a4c8 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -341,6 +341,17 @@ public class SystemUiProxy implements ISystemUiProxy, } } + @Override + public void notifySwipeUpGestureStarted() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifySwipeUpGestureStarted(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifySwipeUpGestureStarted", e); + } + } + } + /** * Notifies that swipe-to-home action is finished. */ @@ -350,18 +361,18 @@ public class SystemUiProxy implements ISystemUiProxy, try { mSystemUiProxy.notifySwipeToHomeFinished(); } catch (RemoteException e) { - Log.w(TAG, "Failed call setPinnedStackAnimationType", e); + Log.w(TAG, "Failed call notifySwipeToHomeFinished", e); } } } @Override - public void onQuickSwitchToNewTask(int rotation) { + public void notifyPrioritizedRotation(int rotation) { if (mSystemUiProxy != null) { try { - mSystemUiProxy.onQuickSwitchToNewTask(rotation); + mSystemUiProxy.notifyPrioritizedRotation(rotation); } catch (RemoteException e) { - Log.w(TAG, "Failed call onQuickSwitchToNewTask with arg: " + rotation, e); + Log.w(TAG, "Failed call notifyPrioritizedRotation with arg: " + rotation, e); } } } From d312651f792fa72005a5f44c9ffe64f1a184bfaa Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Thu, 20 May 2021 09:13:50 -0500 Subject: [PATCH 10/22] Avoid drawing tall header when scrolling search results Bug: 184946772 Test: manual Change-Id: I1bedec89fb9eb5dc51b8b34511a02868cec91efe --- src/com/android/launcher3/allapps/FloatingHeaderView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java index af7896ac24..a0c598aa6e 100644 --- a/src/com/android/launcher3/allapps/FloatingHeaderView.java +++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java @@ -298,7 +298,7 @@ public class FloatingHeaderView extends LinearLayout implements @Override protected void dispatchDraw(Canvas canvas) { - if (mHeaderCollapsed && mTabLayout.getVisibility() == VISIBLE + if (mHeaderCollapsed && !mCollapsed && mTabLayout.getVisibility() == VISIBLE && mHeaderColor != Color.TRANSPARENT) { mBGPaint.setColor(mHeaderColor); mBGPaint.setAlpha((int) (255 * mHeaderAnimator.getAnimatedFraction())); From 1fb271f6e1b01b3ceef6f02f6f34d8f4427f9ea5 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 20 May 2021 10:43:23 -0700 Subject: [PATCH 11/22] Fixing some errorprone build rules Bug: 186259590 Test: Presubmit Change-Id: I3a4a306a0351a67d53e35a6919296dbda8649766 --- Android.bp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Android.bp b/Android.bp index 7e6a565f46..1b6ffe42b7 100644 --- a/Android.bp +++ b/Android.bp @@ -16,6 +16,8 @@ package { default_applicable_licenses: ["packages_apps_Launcher3_license"], } +min_launcher3_sdk_version = "26" + // Added automatically by a large-scale-change // See: http://go/android-license-faq license { @@ -92,7 +94,7 @@ java_library { srcs: ["src_plugins/**/*.java"], sdk_version: "current", - min_sdk_version: "28", + min_sdk_version: min_launcher3_sdk_version, } // Library with all the dependencies for building Launcher3 @@ -114,7 +116,7 @@ android_library { ], manifest: "AndroidManifest-common.xml", sdk_version: "current", - min_sdk_version: "26", + min_sdk_version: min_launcher3_sdk_version, lint: { baseline_filename: "lint-baseline-res-lib.xml", }, @@ -128,7 +130,7 @@ android_library { srcs: ["src_build_config/**/*.java"], static_libs: ["Launcher3ResLib"], sdk_version: "current", - min_sdk_version: "26", + min_sdk_version: min_launcher3_sdk_version, manifest: "AndroidManifest-common.xml", lint: { baseline_filename: "lint-baseline-common-deps-lib.xml", @@ -160,8 +162,8 @@ android_app { }, sdk_version: "current", - min_sdk_version: "26", - target_sdk_version: "29", + min_sdk_version: min_launcher3_sdk_version, + target_sdk_version: "current", privileged: true, system_ext_specific: true, @@ -195,7 +197,7 @@ android_library { "SystemUI-statsd", ], manifest: "quickstep/AndroidManifest.xml", - min_sdk_version: "28", + min_sdk_version: "current", } @@ -244,7 +246,7 @@ android_library { "go/AndroidManifest.xml", "AndroidManifest-common.xml", ], - min_sdk_version: "29", + min_sdk_version: "current", lint: { baseline_filename: "lint-baseline-go-res-lib.xml", }, From 1b8bbb6c40f02edcd741d8b969601cb354030833 Mon Sep 17 00:00:00 2001 From: Pierre Barbier de Reuille Date: Wed, 19 May 2021 22:45:16 +0100 Subject: [PATCH 12/22] Correct size of AppWidget for color extraction. Correct the size of the App Widget: first, compute the size when the widget is positioned (it wasn't done), then do not apply the transformation when computing the size of the widget when drawn. Note: Somehow, there is a shift by 1 on the position on the x axis, both in landscape and portrait. So I added a +1 to get the exact same values. If not, we have a risk of getting different colors. Fix: 188759989 Bug: 187907544 Test: Added logs and moved a widget by hand, check the actual coordinates with hsv. Change-Id: Ia3bd5b3e3ea9a3ef75f8cdf466a9800786646a94 --- src/com/android/launcher3/CellLayout.java | 7 +++++-- .../launcher3/widget/LauncherAppWidgetHostView.java | 10 +++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 1df9df6763..bfa1769ea8 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -1080,9 +1080,10 @@ public class CellLayout extends ViewGroup { cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect); // Now get the rect in drag layer coordinates. - getBoundsForViewInDragLayer(launcher.getDragLayer(), workspace, mTempRect, false, + getBoundsForViewInDragLayer(launcher.getDragLayer(), this, mTempRect, true, mTmpFloatArray, mTempRectF); Utilities.setRect(mTempRectF, mTempRect); + ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, pageId); } } @@ -2594,7 +2595,9 @@ public class CellLayout extends ViewGroup { final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; - final int hStartPadding = getPaddingLeft(); + // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates + final int hStartPadding = getPaddingLeft() + + (int) Math.ceil(getUnusedHorizontalSpace() / 2f); final int vStartPadding = getPaddingTop(); int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth); diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 5deecd4c8a..8685aae737 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -319,11 +319,15 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } mIsScrollable = checkScrollableRecursively(this); - if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) { + mCurrentWidgetSize.left = left; + mCurrentWidgetSize.right = right; + mCurrentWidgetSize.top = top; + mCurrentWidgetSize.bottom = bottom; + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); - getBoundsForViewInDragLayer(mLauncher.getDragLayer(), this, mCurrentWidgetSize, true, - mTmpFloatArray, mTempRectF); + getBoundsForViewInDragLayer(mLauncher.getDragLayer(), (View) getParent(), + mCurrentWidgetSize, true, mTmpFloatArray, mTempRectF); setRect(mTempRectF, mCurrentWidgetSize); updateColorExtraction(mCurrentWidgetSize, mWorkspace.getPageIndexForScreenId(info.screenId)); From 400493c70c96b0a4485193203c011a9dfffe919a Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Wed, 19 May 2021 15:14:05 +0100 Subject: [PATCH 13/22] Apply android:attr/dialogCornerRadius to widgets picker dialog top corners Test: Open full & bottom widgets picker and observe the top corners. Bug: 188651333 Change-Id: If091e2d760042582bb98cbd3e44663542098ecd9 --- .../widgets_bottom_sheet_background.xml | 26 +++++++++++++++++++ .../widgets_bottom_sheet_background.xml | 4 +-- .../launcher3/views/TopRoundedCornerView.java | 3 +-- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 res/drawable-v28/widgets_bottom_sheet_background.xml diff --git a/res/drawable-v28/widgets_bottom_sheet_background.xml b/res/drawable-v28/widgets_bottom_sheet_background.xml new file mode 100644 index 0000000000..c3009c3752 --- /dev/null +++ b/res/drawable-v28/widgets_bottom_sheet_background.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/widgets_bottom_sheet_background.xml index faa414c506..24607678c6 100644 --- a/res/drawable/widgets_bottom_sheet_background.xml +++ b/res/drawable/widgets_bottom_sheet_background.xml @@ -18,8 +18,8 @@ android:shape="rectangle"> diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java index 7888b08630..5519df1f49 100644 --- a/src/com/android/launcher3/views/TopRoundedCornerView.java +++ b/src/com/android/launcher3/views/TopRoundedCornerView.java @@ -21,7 +21,6 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; -import android.widget.FrameLayout; import com.android.launcher3.R; import com.android.launcher3.util.Themes; @@ -41,7 +40,7 @@ public class TopRoundedCornerView extends SpringRelativeLayout { public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - int radius = getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius); + float radius = Themes.getDialogCornerRadius(context); mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0}; mNavBarScrimPaint = new Paint(); From 0c6341af99b328de269aceced5f1c9e0fd0c9d87 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Wed, 19 May 2021 20:06:42 -0700 Subject: [PATCH 14/22] Make launcher opaque when scrim becomes opaque This will help with overdraw, because we don't need to draw the wallpaper. Test: manual Test: adb shell dumpsys SurfaceFlinger --timestats -dump Bug: 187703092 Change-Id: I2ebae94725578e5f4d640cd6b45da3f4d1f21a20 --- .../launcher3/statehandlers/DepthController.java | 12 +++--------- src/com/android/launcher3/views/ScrimView.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index 59d0afa47f..e608885491 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -217,17 +217,11 @@ public class DepthController implements StateHandler, } if (supportsBlur) { - final int blur; - if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { - // All apps has a solid background. We don't need to draw blurs after it's fully - // visible. This will take us out of GPU composition, saving battery and increasing - // performance. - blur = 0; - } else { - blur = (int) (mDepth * mMaxBlurRadius); - } + boolean isOpaque = mLauncher.getScrimView().isFullyOpaque(); + int blur = isOpaque ? 0 : (int) (mDepth * mMaxBlurRadius); new TransactionCompat() .setBackgroundBlurRadius(mSurface, blur) + .setOpaque(mSurface, isOpaque) .apply(); } } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 0ba94ab240..fb1485b480 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -19,6 +19,7 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; @@ -39,6 +40,8 @@ public class ScrimView extends View implements Insettable { private SystemUiController mSystemUiController; private ScrimDrawingController mDrawingController; + private int mBackgroundColor; + private boolean mIsVisible = true; public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); @@ -62,10 +65,21 @@ public class ScrimView extends View implements Insettable { @Override public void setBackgroundColor(int color) { + mBackgroundColor = color; updateSysUiColors(); super.setBackgroundColor(color); } + @Override + public void onVisibilityAggregated(boolean isVisible) { + super.onVisibilityAggregated(isVisible); + mIsVisible = isVisible; + } + + public boolean isFullyOpaque() { + return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255; + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); From df4241ca4e8aa90f381ad7f414b60038111d6707 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 19 May 2021 19:42:14 -0700 Subject: [PATCH 15/22] Fixing Launcher preview leaking surface and memory > Closing existing preview if a new request comes for same host token > Closing in-memory icon db when closing preview > Removing unnecessary wait blocks on UI thread and rendering view asynchronously > Fixing preview loading failing on LauncherAppState access Bug: 186712316 Bug: 187140897 Test: Manual Change-Id: I045930b007e5dc015320224a197eee20a8354d17 --- .../android/launcher3/LauncherAppState.java | 5 +- .../graphics/GridCustomizationsProvider.java | 84 ++++++- .../graphics/LauncherPreviewRenderer.java | 200 +++------------- .../graphics/PreviewSurfaceRenderer.java | 218 ++++++++++-------- .../android/launcher3/icons/IconCache.java | 7 + .../android/launcher3/model/ModelPreload.java | 12 +- 6 files changed, 255 insertions(+), 271 deletions(-) diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index b6cc6d644d..dabbdd3063 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -81,6 +81,8 @@ public class LauncherAppState { public LauncherAppState(Context context) { this(context, LauncherFiles.APP_ICONS_DB); + Log.v(Launcher.TAG, "LauncherAppState initiated"); + Preconditions.assertUIThread(); mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher()); @@ -132,8 +134,6 @@ public class LauncherAppState { } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { - Log.v(Launcher.TAG, "LauncherAppState initiated"); - Preconditions.assertUIThread(); mContext = context; mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); @@ -142,6 +142,7 @@ public class LauncherAppState { iconCacheFileName, mIconProvider); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext)); + mOnTerminateCallback.add(mIconCache::close); } private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java index a03e48d29a..60a1732bb6 100644 --- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java +++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java @@ -4,6 +4,7 @@ import static com.android.launcher3.Utilities.getPrefs; import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS; import static com.android.launcher3.util.Themes.isThemedIconEnabled; +import android.annotation.TargetApi; import android.content.ContentProvider; import android.content.ContentValues; import android.content.pm.PackageManager; @@ -12,14 +13,24 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.util.ArrayMap; import android.util.Log; import android.util.Xml; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.GridOption; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.Executors; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,6 +76,11 @@ public class GridCustomizationsProvider extends ContentProvider { private static final String ICON_THEMED = "/icon_themed"; private static final String BOOLEAN_VALUE = "boolean_value"; + private static final String KEY_SURFACE_PACKAGE = "surface_package"; + private static final String KEY_CALLBACK = "callback"; + + private final ArrayMap mActivePreviews = new ArrayMap<>(); + @Override public boolean onCreate() { return true; @@ -177,10 +193,74 @@ public class GridCustomizationsProvider extends ContentProvider { return null; } - if (!METHOD_GET_PREVIEW.equals(method)) { + if (!Utilities.ATLEAST_R || !METHOD_GET_PREVIEW.equals(method)) { return null; } + return getPreview(extras); + } - return new PreviewSurfaceRenderer(getContext(), extras).render(); + @TargetApi(Build.VERSION_CODES.R) + private synchronized Bundle getPreview(Bundle request) { + PreviewLifecycleObserver observer = null; + try { + PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request); + + // Destroy previous + destroyObserver(mActivePreviews.get(renderer.getHostToken())); + + observer = new PreviewLifecycleObserver(renderer); + mActivePreviews.put(renderer.getHostToken(), observer); + + renderer.loadAsync(); + renderer.getHostToken().linkToDeath(observer, 0); + + Bundle result = new Bundle(); + result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage()); + + Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(), observer)); + Message msg = Message.obtain(); + msg.replyTo = messenger; + result.putParcelable(KEY_CALLBACK, msg); + return result; + } catch (Exception e) { + Log.e(TAG, "Unable to generate preview", e); + if (observer != null) { + destroyObserver(observer); + } + return null; + } + } + + private synchronized void destroyObserver(PreviewLifecycleObserver observer) { + if (observer == null || observer.destroyed) { + return; + } + observer.destroyed = true; + Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy); + PreviewLifecycleObserver cached = mActivePreviews.get(observer.renderer.getHostToken()); + if (cached == observer) { + mActivePreviews.remove(observer.renderer.getHostToken()); + } + } + + private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient { + + public final PreviewSurfaceRenderer renderer; + public boolean destroyed = false; + + PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) { + this.renderer = renderer; + } + + @Override + public boolean handleMessage(Message message) { + destroyObserver(this); + return true; + } + + @Override + public void binderDied() { + destroyObserver(this); + } } } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index f5b6890867..5f014db1ea 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -23,7 +23,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; import android.app.Fragment; @@ -32,7 +31,6 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.pm.ShortcutInfo; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; @@ -43,7 +41,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Process; import android.util.AttributeSet; -import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -57,8 +54,6 @@ import com.android.launcher3.Hotseat; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; import com.android.launcher3.WorkspaceLayoutManager; @@ -67,13 +62,8 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.FixedContainerItems; -import com.android.launcher3.model.LoaderResults; -import com.android.launcher3.model.LoaderTask; -import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.FolderInfo; @@ -100,13 +90,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. @@ -120,8 +104,6 @@ import java.util.concurrent.TimeoutException; public class LauncherPreviewRenderer extends ContextWrapper implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { - private static final String TAG = "LauncherPreviewRenderer"; - /** * Context used just for preview. It also provides a few objects (e.g. UserCache) just for * preview purposes. @@ -138,9 +120,15 @@ public class LauncherPreviewRenderer extends ContextWrapper private final ConcurrentLinkedQueue mIconPool = new ConcurrentLinkedQueue<>(); + private boolean mDestroyed = false; + public PreviewContext(Context base, InvariantDeviceProfile idp) { super(base); mIdp = idp; + mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); + mObjectMap.put(LauncherAppState.INSTANCE, + new LauncherAppState(this, null /* iconCacheFileName */)); + } @Override @@ -149,11 +137,9 @@ public class LauncherPreviewRenderer extends ContextWrapper } public void onDestroy() { - CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get( - CustomWidgetManager.INSTANCE); - if (customWidgetManager != null) { - customWidgetManager.onDestroy(); - } + CustomWidgetManager.INSTANCE.get(this).onDestroy(); + LauncherAppState.INSTANCE.get(this).onTerminate(); + mDestroyed = true; } /** @@ -162,17 +148,12 @@ public class LauncherPreviewRenderer extends ContextWrapper */ public T getObject(MainThreadInitializedObject mainThreadInitializedObject, MainThreadInitializedObject.ObjectProvider provider) { + if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) { + throw new RuntimeException("Context already destroyed"); + } if (!mAllowedObjects.contains(mainThreadInitializedObject)) { throw new IllegalStateException("Leaking unknown objects"); } - if (mainThreadInitializedObject == LauncherAppState.INSTANCE) { - throw new IllegalStateException( - "Should not use MainThreadInitializedObject to initialize this with " - + "PreviewContext"); - } - if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) { - return (T) mIdp; - } if (mObjectMap.containsKey(mainThreadInitializedObject)) { return (T) mObjectMap.get(mainThreadInitializedObject); } @@ -210,7 +191,6 @@ public class LauncherPreviewRenderer extends ContextWrapper private final Context mContext; private final InvariantDeviceProfile mIdp; private final DeviceProfile mDp; - private final boolean mMigrated; private final Rect mInsets; private final WorkspaceItemInfo mWorkspaceItemInfo; private final LayoutInflater mHomeElementInflater; @@ -218,13 +198,12 @@ public class LauncherPreviewRenderer extends ContextWrapper private final Hotseat mHotseat; private final CellLayout mWorkspace; - public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) { + public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) { super(context); mUiHandler = new Handler(Looper.getMainLooper()); mContext = context; mIdp = idp; mDp = idp.getDeviceProfile(context).copy(context); - mMigrated = migrated; // TODO: get correct insets once display cutout API is available. mInsets = new Rect(); @@ -265,8 +244,9 @@ public class LauncherPreviewRenderer extends ContextWrapper } /** Populate preview and render it. */ - public View getRenderedView() { - populate(); + public View getRenderedView(BgDataModel dataModel, + Map widgetProviderInfoMap) { + populate(dataModel, widgetProviderInfoMap); return mRootView; } @@ -392,38 +372,17 @@ public class LauncherPreviewRenderer extends ContextWrapper } } - private void populate() { - WorkspaceFetcher fetcher; - PreviewContext previewContext = null; - if (mMigrated) { - previewContext = new PreviewContext(mContext, mIdp); - LauncherAppState appForPreview = new LauncherAppState( - previewContext, null /* iconCacheFileName */); - fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview); - MODEL_EXECUTOR.execute(fetcher); - } else { - fetcher = new WorkspaceItemsInfoFetcher(); - LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask( - (LauncherModel.ModelUpdateTask) fetcher); - } - WorkspaceResult workspaceResult = fetcher.get(); - if (previewContext != null) { - previewContext.onDestroy(); - } - - if (workspaceResult == null) { - return; - } - + private void populate(BgDataModel dataModel, + Map widgetProviderInfoMap) { // Separate the items that are on the current screen, and the other remaining items. ArrayList currentWorkspaceItems = new ArrayList<>(); ArrayList otherWorkspaceItems = new ArrayList<>(); ArrayList currentAppWidgets = new ArrayList<>(); ArrayList otherAppWidgets = new ArrayList<>(); filterCurrentWorkspaceItems(0 /* currentScreenId */, - workspaceResult.mWorkspaceItems, currentWorkspaceItems, + dataModel.workspaceItems, currentWorkspaceItems, otherWorkspaceItems); - filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets, + filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets, currentAppWidgets, otherAppWidgets); sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems); for (ItemInfo itemInfo : currentWorkspaceItems) { @@ -444,12 +403,12 @@ public class LauncherPreviewRenderer extends ContextWrapper switch (itemInfo.itemType) { case Favorites.ITEM_TYPE_APPWIDGET: case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - if (mMigrated) { - inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, - workspaceResult.mWidgetProvidersMap); + if (widgetProviderInfoMap != null) { + inflateAndAddWidgets( + (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap); } else { inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, - workspaceResult.mWidgetsModel); + dataModel.widgetsModel); } break; default: @@ -458,8 +417,10 @@ public class LauncherPreviewRenderer extends ContextWrapper } IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, mDp.numShownHotseatIcons); - List predictions = workspaceResult.mHotseatPredictions == null - ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items; + FixedContainerItems hotseatpredictions = + dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); + List predictions = hotseatpredictions == null + ? Collections.emptyList() : hotseatpredictions.items; int count = Math.min(ranks.size(), predictions.size()); for (int i = 0; i < count; i++) { int rank = ranks.get(i); @@ -494,109 +455,4 @@ public class LauncherPreviewRenderer extends ContextWrapper view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); view.layout(0, 0, width, height); } - - private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask, - WorkspaceFetcher { - - private final FutureTask mTask = new FutureTask<>(this); - - private LauncherAppState mApp; - private LauncherModel mModel; - private BgDataModel mBgDataModel; - private AllAppsList mAllAppsList; - - @Override - public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, - AllAppsList allAppsList, Executor uiExecutor) { - mApp = app; - mModel = model; - mBgDataModel = dataModel; - mAllAppsList = allAppsList; - } - - @Override - public FutureTask getTask() { - return mTask; - } - - @Override - public void run() { - mTask.run(); - } - - @Override - public WorkspaceResult call() throws Exception { - if (!mModel.isModelLoaded()) { - Log.d(TAG, "Workspace not loaded, loading now"); - mModel.startLoaderForResults( - new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0])); - return null; - } - - return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null); - } - } - - private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements - WorkspaceFetcher { - - private final FutureTask mTask = new FutureTask<>(this); - - WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) { - super(app, null, new BgDataModel(), new ModelDelegate(), null); - } - - @Override - public FutureTask getTask() { - return mTask; - } - - @Override - public void run() { - mTask.run(); - } - - @Override - public WorkspaceResult call() { - List allShortcuts = new ArrayList<>(); - loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI, - LauncherSettings.Favorites.SCREEN + " = 0 or " - + LauncherSettings.Favorites.CONTAINER + " = " - + LauncherSettings.Favorites.CONTAINER_HOTSEAT); - return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap); - } - } - - private interface WorkspaceFetcher extends Runnable, Callable { - FutureTask getTask(); - - default WorkspaceResult get() { - try { - return getTask().get(5, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Log.d(TAG, "Error fetching workspace items info", e); - return null; - } - } - } - - private static class WorkspaceResult { - private final ArrayList mWorkspaceItems; - private final ArrayList mAppWidgets; - private final FixedContainerItems mHotseatPredictions; - private final WidgetsModel mWidgetsModel; - private final Map mWidgetProvidersMap; - - private WorkspaceResult(BgDataModel dataModel, - WidgetsModel widgetsModel, - Map widgetProviderInfoMap) { - synchronized (dataModel) { - mWorkspaceItems = dataModel.workspaceItems; - mAppWidgets = dataModel.appWidgets; - mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); - mWidgetsModel = widgetsModel; - mWidgetProvidersMap = widgetProviderInfoMap; - } - } - } } diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 6193570e1f..8c39eaeddb 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -21,32 +21,47 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.app.WallpaperColors; +import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; +import android.util.Log; import android.view.ContextThemeWrapper; import android.view.Display; import android.view.SurfaceControlViewHost; +import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; +import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.GridSizeMigrationTaskV2; +import com.android.launcher3.model.LoaderTask; +import com.android.launcher3.model.ModelDelegate; +import com.android.launcher3.model.ModelPreload; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Themes; import com.android.launcher3.widget.LocalColorExtractor; +import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.TimeUnit; /** Render preview using surface view. */ @SuppressWarnings("NewApi") -public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { +public class PreviewSurfaceRenderer { + + private static final String TAG = "PreviewSurfaceRenderer"; private static final int FADE_IN_ANIMATION_DURATION = 200; @@ -54,8 +69,6 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { private static final String KEY_VIEW_WIDTH = "width"; private static final String KEY_VIEW_HEIGHT = "height"; private static final String KEY_DISPLAY_ID = "display_id"; - private static final String KEY_SURFACE_PACKAGE = "surface_package"; - private static final String KEY_CALLBACK = "callback"; private static final String KEY_COLORS = "wallpaper_colors"; private final Context mContext; @@ -65,10 +78,13 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { private final int mHeight; private final Display mDisplay; private final WallpaperColors mWallpaperColors; + private final RunnableList mOnDestroyCallbacks = new RunnableList(); - private SurfaceControlViewHost mSurfaceControlViewHost; + private final SurfaceControlViewHost mSurfaceControlViewHost; - PreviewSurfaceRenderer(Context context, Bundle bundle) { + private boolean mDestroyed = false; + + public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception { mContext = context; String gridName = bundle.getString("name"); @@ -77,106 +93,97 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { gridName = InvariantDeviceProfile.getCurrentGridName(context); } mWallpaperColors = bundle.getParcelable(KEY_COLORS); - mIdp = new InvariantDeviceProfile(context, gridName); mHostToken = bundle.getBinder(KEY_HOST_TOKEN); mWidth = bundle.getInt(KEY_VIEW_WIDTH); mHeight = bundle.getInt(KEY_VIEW_HEIGHT); + mDisplay = context.getSystemService(DisplayManager.class) + .getDisplay(bundle.getInt(KEY_DISPLAY_ID)); - final DisplayManager displayManager = (DisplayManager) context.getSystemService( - Context.DISPLAY_SERVICE); - mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)); + mSurfaceControlViewHost = MAIN_EXECUTOR + .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) + .get(5, TimeUnit.SECONDS); + mOnDestroyCallbacks.add(mSurfaceControlViewHost::release); } - /** Handle a received surface view request. */ - Bundle render() { - if (mSurfaceControlViewHost != null) { - binderDied(); + public IBinder getHostToken() { + return mHostToken; + } + + public SurfacePackage getSurfacePackage() { + return mSurfaceControlViewHost.getSurfacePackage(); + } + + /** + * Destroys the preview and all associated data + */ + @UiThread + public void destroy() { + mDestroyed = true; + mOnDestroyCallbacks.executeAllAndDestroy(); + } + + /** + * Generates the preview in background + */ + public void loadAsync() { + MODEL_EXECUTOR.execute(this::loadModelData); + } + + @WorkerThread + private void loadModelData() { + final boolean migrated = doGridMigrationIfNecessary(); + + final Context inflationContext; + if (mWallpaperColors != null) { + // Create a themed context, without affecting the main application context + Context context = mContext.createDisplayContext(mDisplay); + LocalColorExtractor.newInstance(mContext) + .applyColorsOverride(context, mWallpaperColors); + inflationContext = new ContextThemeWrapper(context, + Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); + } else { + inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme); } - SurfaceControlViewHost.SurfacePackage surfacePackage; - try { - mSurfaceControlViewHost = MAIN_EXECUTOR - .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) - .get(5, TimeUnit.SECONDS); - surfacePackage = mSurfaceControlViewHost.getSurfacePackage(); - mHostToken.linkToDeath(this, 0); - } catch (Exception e) { - e.printStackTrace(); - return null; - } + if (migrated) { + PreviewContext previewContext = new PreviewContext(inflationContext, mIdp); + new LoaderTask( + LauncherAppState.getInstance(previewContext), + null, + new BgDataModel(), + new ModelDelegate(), null) { - MODEL_EXECUTOR.post(() -> { - final boolean success = doGridMigrationIfNecessary(); - - final Context inflationContext; - if (mWallpaperColors != null) { - // Workaround to create a themed context - Context context = mContext.createDisplayContext(mDisplay); - LocalColorExtractor.newInstance(mContext) - .applyColorsOverride(context, mWallpaperColors); - - inflationContext = new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); - } else { - inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme); - } - - MAIN_EXECUTOR.post(() -> { - // If mSurfaceControlViewHost is null due to any reason (e.g. binder died, - // happening when user leaves the preview screen before preview rendering finishes), - // we should return here. - SurfaceControlViewHost host = mSurfaceControlViewHost; - if (host == null) { - return; + @Override + public void run() { + loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI, + LauncherSettings.Favorites.SCREEN + " = 0 or " + + LauncherSettings.Favorites.CONTAINER + " = " + + LauncherSettings.Favorites.CONTAINER_HOTSEAT); + MAIN_EXECUTOR.execute(() -> { + renderView(previewContext, mBgDataModel, mWidgetProvidersMap); + mOnDestroyCallbacks.add(previewContext::onDestroy); + }); } + }.run(); + } else { + new ModelPreload() { - View view = new LauncherPreviewRenderer(inflationContext, mIdp, success) - .getRenderedView(); - // This aspect scales the view to fit in the surface and centers it - final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), - mHeight / (float) view.getMeasuredHeight()); - view.setScaleX(scale); - view.setScaleY(scale); - view.setPivotX(0); - view.setPivotY(0); - view.setTranslationX((mWidth - scale * view.getWidth()) / 2); - view.setTranslationY((mHeight - scale * view.getHeight()) / 2); - view.setAlpha(0); - view.animate().alpha(1) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(FADE_IN_ANIMATION_DURATION) - .start(); - host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); - }); - }); - - Bundle result = new Bundle(); - result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage); - - Handler handler = new Handler(Looper.getMainLooper(), message -> { - binderDied(); - return true; - }); - Messenger messenger = new Messenger(handler); - Message msg = Message.obtain(); - msg.replyTo = messenger; - result.putParcelable(KEY_CALLBACK, msg); - return result; - } - - @Override - public void binderDied() { - if (mSurfaceControlViewHost != null) { - MAIN_EXECUTOR.execute(() -> { - mSurfaceControlViewHost.release(); - mSurfaceControlViewHost = null; - }); + @Override + public void onComplete(boolean isSuccess) { + if (isSuccess) { + MAIN_EXECUTOR.execute(() -> + renderView(inflationContext, getBgDataModel(), null)); + } else { + Log.e(TAG, "Model loading failed"); + } + } + }.start(inflationContext); } - mHostToken.unlinkToDeath(this, 0); } + @WorkerThread private boolean doGridMigrationIfNecessary() { boolean needsToMigrate = MULTI_DB_GRID_MIRATION_ALGO.get() @@ -189,4 +196,29 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp) : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp); } + + @UiThread + private void renderView(Context inflationContext, BgDataModel dataModel, + Map widgetProviderInfoMap) { + if (mDestroyed) { + return; + } + View view = new LauncherPreviewRenderer(inflationContext, mIdp) + .getRenderedView(dataModel, widgetProviderInfoMap); + // This aspect scales the view to fit in the surface and centers it + final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), + mHeight / (float) view.getMeasuredHeight()); + view.setScaleX(scale); + view.setScaleY(scale); + view.setPivotX(0); + view.setPivotY(0); + view.setTranslationX((mWidth - scale * view.getWidth()) / 2); + view.setTranslationY((mHeight - scale * view.getHeight()) / 2); + view.setAlpha(0); + view.animate().alpha(1) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(FADE_IN_ANIMATION_DURATION) + .start(); + mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); + } } diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 297325a0ee..8e0a388784 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -130,6 +130,13 @@ public class IconCache extends BaseIconCache { } } + /** + * Closes the cache DB. This will clear any in-memory cache. + */ + public void close() { + mIconDb.close(); + } + /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java index 713492b9fa..756b7da759 100644 --- a/src/com/android/launcher3/model/ModelPreload.java +++ b/src/com/android/launcher3/model/ModelPreload.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.util.Log; @@ -52,8 +54,14 @@ public class ModelPreload implements ModelUpdateTask { public final void run() { mModel.startLoaderForResultsIfNotLoaded( new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0])); - Log.d(TAG, "Preload completed : " + mModel.isModelLoaded()); - onComplete(mModel.isModelLoaded()); + MODEL_EXECUTOR.post(() -> { + Log.d(TAG, "Preload completed : " + mModel.isModelLoaded()); + onComplete(mModel.isModelLoaded()); + }); + } + + public BgDataModel getBgDataModel() { + return mBgDataModel; } /** From bd2fa26bdaa2e80f31eaef7ad6cdea83d5bd111c Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 20 May 2021 13:17:44 -0700 Subject: [PATCH 16/22] Tweak ordering of first match search - Tweak iteration for finding first match, go by operator priority and check each cell layout by order to return the first view that matches - Also reset the launch cookie in cases where we're launching a task that doesn't support cookies (ie. via prediction), otherwise we may still try to associate it with the last launched cookie later Fixes: 187720286 Test: Swipe up from various icon types across hotseat/workspace Change-Id: I71c4c13c442b0884b2247589685f976eaaeb30a4 Signed-off-by: Winson Chung --- .../launcher3/BaseQuickstepLauncher.java | 5 ++ .../quickstep/LauncherSwipeHandlerV2.java | 3 +- src/com/android/launcher3/Workspace.java | 48 +++++++------------ .../launcher3/model/data/ItemInfo.java | 2 + 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index e777ee7354..25e23594ac 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -20,6 +20,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NO_OFFSET; +import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; @@ -499,6 +500,8 @@ public abstract class BaseQuickstepLauncher extends Launcher // Also allow swiping to folders break; } + // Reset any existing launch cookies associated with the cookie + opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); return; } switch (info.itemType) { @@ -509,6 +512,8 @@ public abstract class BaseQuickstepLauncher extends Launcher // Fall through and continue if it's an app, shortcut, or widget break; default: + // Reset any existing launch cookies associated with the cookie + opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); return; } opts.setLaunchCookie(ObjectWrapper.wrap(new Integer(info.id))); diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index 811af7e658..1bae1c5f6b 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.boundToRange; import static com.android.launcher3.Utilities.dpToPx; import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; +import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; @@ -234,7 +235,7 @@ public class LauncherSwipeHandlerV2 extends // Find the associated item info for the launch cookie (if available), note that predicted // apps actually have an id of -1, so use another default id here - int launchCookieItemId = -2; + int launchCookieItemId = NO_MATCHING_ID; for (IBinder cookie : launchCookies) { Integer itemId = ObjectWrapper.unwrap(cookie); if (itemId != null) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 5ba7623ece..98d80fedbc 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2953,10 +2953,11 @@ public class Workspace extends PagedView }; final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) -> info != null - && info.getTargetComponent() != null - && TextUtils.equals(info.getTargetComponent().getPackageName(), packageName) + && info.itemType == ITEM_TYPE_APPLICATION && info.user.equals(user) - && info.itemType == ITEM_TYPE_APPLICATION; + && info.getTargetComponent() != null + && TextUtils.equals(info.getTargetComponent().getPackageName(), + packageName); final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> { if (info instanceof FolderInfo) { FolderInfo folderInfo = (FolderInfo) info; @@ -2973,7 +2974,7 @@ public class Workspace extends PagedView cellLayouts.add(getHotseat()); forEachVisiblePage(page -> cellLayouts.add((CellLayout) page)); - // Order: App icons, app in folder. Items in hotseat get returned first. + // Order: Preferred item, App icons in hotseat/workspace, app in folder in hotseat/workspace if (ADAPTIVE_ICON_WINDOW_ANIM.get()) { return getFirstMatch(cellLayouts, preferredItem, preferredItemInFolder, packageAndUserAndApp, packageAndUserAndAppInFolder); @@ -3010,34 +3011,17 @@ public class Workspace extends PagedView } /** + * Finds the first view matching the ordered operators across the given cell layouts by order. * @param cellLayouts List of CellLayouts to scan, in order of preference. * @param operators List of operators, in order starting from best matching operator. - * @return */ View getFirstMatch(Iterable cellLayouts, final ItemOperator... operators) { - // This array is filled with the first match for each operator. - final View[] matches = new View[operators.length]; - // For efficiency, the outer loop should be CellLayout. - for (CellLayout cellLayout : cellLayouts) { - mapOverCellLayout(cellLayout, (info, v) -> { - for (int i = 0; i < operators.length; ++i) { - if (matches[i] == null && operators[i].evaluate(info, v)) { - matches[i] = v; - if (i == 0) { - // We can return since this is the best match possible. - return true; - } - } + for (ItemOperator operator : operators) { + for (CellLayout cellLayout : cellLayouts) { + View match = mapOverCellLayout(cellLayout, operator); + if (match != null) { + return match; } - return false; - }); - if (matches[0] != null) { - break; - } - } - for (View match : matches) { - if (match != null) { - return match; } } return null; @@ -3111,16 +3095,16 @@ public class Workspace extends PagedView */ public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - if (mapOverCellLayout(layout, op)) { + if (mapOverCellLayout(layout, op) != null) { return; } } } - private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) { + private View mapOverCellLayout(CellLayout layout, ItemOperator op) { // TODO(b/128460496) Potential race condition where layout is not yet loaded if (layout == null) { - return false; + return null; } ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); // map over all the shortcuts on the workspace @@ -3128,10 +3112,10 @@ public class Workspace extends PagedView for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { View item = container.getChildAt(itemIdx); if (op.evaluate((ItemInfo) item.getTag(), item)) { - return true; + return item; } } - return false; + return null; } void updateShortcuts(List shortcuts) { diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index e388965e91..3abcc2b826 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -68,6 +68,8 @@ public class ItemInfo { public static final boolean DEBUG = false; public static final int NO_ID = -1; + // An id that doesn't match any item, including predicted apps with have an id=NO_ID + public static final int NO_MATCHING_ID = Integer.MIN_VALUE; /** * The id in the settings database for this item From bfac8730fe1baa8acb4e95c1392ec3cb91a003e4 Mon Sep 17 00:00:00 2001 From: vadimt Date: Thu, 20 May 2021 15:48:29 -0700 Subject: [PATCH 17/22] Improving TAPL messaging Test: presubmit Bug: 187761685 Change-Id: I83eb0eef2f9c8c33cc9d1b42cb52fb670a5bf427 --- .../com/android/launcher3/tapl/LauncherInstrumentation.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 57f40db205..4cf52f071d 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -496,13 +496,14 @@ public final class LauncherInstrumentation { private void fail(String message) { checkForAnomaly(); Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( - "http://go/tapl test failure:\nOverview: " + getContextDescription() + "http://go/tapl test failure:\nSummary: " + getContextDescription() + " - visible state is " + getVisibleStateMessage() + ";\nDetails: " + message, true))); } private String getContextDescription() { - return mDiagnosticContext.isEmpty() ? "" : String.join(", ", mDiagnosticContext); + return mDiagnosticContext.isEmpty() + ? "(no context)" : String.join(", ", mDiagnosticContext); } void assertTrue(String message, boolean condition) { From ccb71776c64c48f87158f851a0a4b4dda563e630 Mon Sep 17 00:00:00 2001 From: Chihhang Chuang Date: Thu, 20 May 2021 01:50:03 +0800 Subject: [PATCH 18/22] Apply the current system window insets to launcher preview For wallpaper picker, the position of app icon on the wallpaper preview would match to the launcher after applying the wallpaper. Wallpaper picker: https://screenshot.googleplex.com/JZazpgZLgzacZMZ.png Launcher: https://screenshot.googleplex.com/9uTTQwdmcnFZ7Vi.png Gesture mode: https://screenshot.googleplex.com/3YaKbBerYgtHBFb.png 3-button mode: https://screenshot.googleplex.com/7sA5nE3GmbuKf62.png Fixes: 188539072 Test: Manually Change-Id: I553000e6ae646207eb94fa33e97d00a3990043c8 --- .../graphics/LauncherPreviewRenderer.java | 20 +++++++++++++++---- .../graphics/PreviewSurfaceRenderer.java | 6 ++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 5f014db1ea..2a1aec84a3 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -45,6 +45,8 @@ import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.TextClock; import com.android.launcher3.BubbleTextView; @@ -56,6 +58,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; @@ -205,10 +208,19 @@ public class LauncherPreviewRenderer extends ContextWrapper mIdp = idp; mDp = idp.getDeviceProfile(context).copy(context); - // TODO: get correct insets once display cutout API is available. - mInsets = new Rect(); - mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; - mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; + if (Utilities.ATLEAST_R) { + WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class) + .getCurrentWindowMetrics().getWindowInsets(); + mInsets = new Rect( + currentWindowInsets.getSystemWindowInsetLeft(), + currentWindowInsets.getSystemWindowInsetTop(), + currentWindowInsets.getSystemWindowInsetRight(), + currentWindowInsets.getSystemWindowInsetBottom()); + } else { + mInsets = new Rect(); + mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; + mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; + } mDp.updateInsets(mInsets); BaseIconFactory iconFactory = diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 8c39eaeddb..a8c3d15bde 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -32,6 +32,7 @@ import android.view.Display; import android.view.SurfaceControlViewHost; import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.View; +import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateDecelerateInterpolator; import androidx.annotation.UiThread; @@ -41,6 +42,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.GridSizeMigrationTask; @@ -139,6 +141,10 @@ public class PreviewSurfaceRenderer { if (mWallpaperColors != null) { // Create a themed context, without affecting the main application context Context context = mContext.createDisplayContext(mDisplay); + if (Utilities.ATLEAST_R) { + context = context.createWindowContext( + LayoutParams.TYPE_APPLICATION_OVERLAY, null); + } LocalColorExtractor.newInstance(mContext) .applyColorsOverride(context, mWallpaperColors); inflationContext = new ContextThemeWrapper(context, From d5500548dca15d4103d1d654fb1bf9fe8cf465a8 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 20 May 2021 20:18:47 +0000 Subject: [PATCH 19/22] Revert^2 "Moving taskbar lifecycle to TouchInteractionService" 430465a3d5ff0dcdd0c2e263d862049d93075a07 Bug: 187353581 Change-Id: I7b2280d16adfafd3e85ffc1d22e32d0c00d12b67 --- quickstep/res/layout/taskbar.xml | 24 +- quickstep/res/layout/taskbar_view.xml | 26 - quickstep/res/values/dimens.xml | 1 + .../launcher3/BaseQuickstepLauncher.java | 92 +--- .../launcher3/QuickstepTransitionManager.java | 3 - .../launcher3/taskbar/ButtonProvider.java | 32 +- .../android/launcher3/taskbar/ImeBarView.java | 17 +- .../taskbar/TaskbarActivityContext.java | 230 ++++++++- .../taskbar/TaskbarAnimationController.java | 5 +- .../taskbar/TaskbarContainerView.java | 94 ++-- .../launcher3/taskbar/TaskbarController.java | 472 +++--------------- .../taskbar/TaskbarDragController.java | 13 +- .../taskbar/TaskbarHotseatController.java | 17 +- .../taskbar/TaskbarIconController.java | 163 ++++++ .../launcher3/taskbar/TaskbarManager.java | 157 ++++++ .../taskbar/TaskbarNavButtonController.java | 20 +- .../taskbar/TaskbarStateHandler.java | 41 +- .../taskbar/TaskbarUIController.java | 41 ++ .../launcher3/taskbar/TaskbarView.java | 262 ++++------ .../quickstep/BaseActivityInterface.java | 13 - .../quickstep/LauncherActivityInterface.java | 14 +- .../quickstep/TouchInteractionService.java | 61 +-- .../util/StaggeredWorkspaceAnim.java | 6 +- res/layout/taskbar_view.xml | 21 - src/com/android/launcher3/BubbleTextView.java | 4 - src/com/android/launcher3/Hotseat.java | 21 +- src/com/android/launcher3/Launcher.java | 14 - src/com/android/launcher3/Utilities.java | 10 +- .../launcher3/touch/ItemClickHandler.java | 56 ++- 29 files changed, 948 insertions(+), 982 deletions(-) delete mode 100644 quickstep/res/layout/taskbar_view.xml create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java delete mode 100644 res/layout/taskbar_view.xml diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index 240fe556da..c4362214cb 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -22,9 +22,29 @@ + android:gravity="center" + android:forceHasOverlappingRendering="false" + android:layout_gravity="bottom" > + + + + + + - - - - diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 735cb24f82..50453ace16 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -149,4 +149,5 @@ 8dp 16dp + 16dp diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 25e23594ac..300654e657 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -21,7 +21,6 @@ import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NO_OFFSET; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; -import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE; @@ -31,7 +30,6 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.ServiceConnection; @@ -52,13 +50,11 @@ import com.android.launcher3.proxy.StartActivityParams; import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarController; +import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.taskbar.TaskbarStateHandler; -import com.android.launcher3.taskbar.TaskbarView; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.RecentsModel; @@ -68,6 +64,7 @@ import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; import com.android.quickstep.TouchInteractionService; +import com.android.quickstep.TouchInteractionService.TISBinder; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.util.SplitSelectStateController; @@ -89,8 +86,6 @@ public abstract class BaseQuickstepLauncher extends Launcher private DepthController mDepthController = new DepthController(this); private QuickstepTransitionManager mAppTransitionManager; - private ServiceConnection mTisBinderConnection; - protected TouchInteractionService.TISBinder mTisBinder; /** * Reusable command for applying the back button alpha on the background thread. @@ -101,8 +96,20 @@ public abstract class BaseQuickstepLauncher extends Launcher private OverviewActionsView mActionsView; + private @Nullable TaskbarManager mTaskbarManager; private @Nullable TaskbarController mTaskbarController; + private final ServiceConnection mTisBinderConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager(); + mTaskbarManager.setLauncher(BaseQuickstepLauncher.this); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { } + }; private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this); + // Will be updated when dragging from taskbar. private @Nullable DragOptions mNextWorkspaceDragOptions = null; private SplitPlaceholderView mSplitPlaceholderView; @@ -112,24 +119,6 @@ public abstract class BaseQuickstepLauncher extends Launcher super.onCreate(savedInstanceState); SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this); addMultiWindowModeChangedListener(mDepthController); - setupTouchInteractionServiceBinder(); - } - - private void setupTouchInteractionServiceBinder() { - Intent intent = new Intent(this, TouchInteractionService.class); - mTisBinderConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder binder) { - mTisBinder = ((TouchInteractionService.TISBinder) binder); - mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - mTisBinder = null; - } - }; - bindService(intent, mTisBinderConnection, 0); } @Override @@ -137,15 +126,12 @@ public abstract class BaseQuickstepLauncher extends Launcher mAppTransitionManager.onActivityDestroyed(); SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); - if (mTaskbarController != null) { - mTaskbarController.cleanup(); - mTaskbarController = null; - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(null); - unbindService(mTisBinderConnection); - } - } + + unbindService(mTisBinderConnection); + if (mTaskbarManager != null) { + mTaskbarManager.setLauncher(null); + } super.onDestroy(); } @@ -272,37 +258,12 @@ public abstract class BaseQuickstepLauncher extends Launcher mAppTransitionManager = new QuickstepTransitionManager(this); mAppTransitionManager.registerRemoteAnimations(); - addTaskbarIfNecessary(); - addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary()); + bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0); + } - @Override - public void onDisplayInfoChanged(Context context, DisplayController.Info info, - int flags) { - super.onDisplayInfoChanged(context, info, flags); - if ((flags & CHANGE_ACTIVE_SCREEN) != 0) { - addTaskbarIfNecessary(); - } - } - - private void addTaskbarIfNecessary() { - if (mTaskbarController != null) { - mTaskbarController.cleanup(); - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(null); - } - mTaskbarController = null; - } - if (mDeviceProfile.isTaskbarPresent) { - TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView(); - TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this); - mTaskbarController = new TaskbarController(this, - taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome); - mTaskbarController.init(); - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); - } - } + public void setTaskbarController(TaskbarController taskbarController) { + mTaskbarController = taskbarController; } public T getActionsView() { @@ -341,14 +302,9 @@ public abstract class BaseQuickstepLauncher extends Launcher } @Override - public boolean isViewInTaskbar(View v) { - return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v); - } - public boolean supportsAdaptiveIconAnimation(View clickedView) { return mAppTransitionManager.hasControlRemoteAppTransitionPermission() - && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get() - && !isViewInTaskbar(clickedView); + && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get(); } @Override diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 1b8fcb3c67..80754a00f4 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -1252,7 +1252,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); - final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV); if (launchingFromWidget) { composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, wallpaperTargets, nonAppTargets); @@ -1263,8 +1262,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener launcherClosing); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); - } else if (launchingFromTaskbar) { - // TODO } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java index 0d4130d1bc..540f748313 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java +++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java @@ -16,12 +16,17 @@ package com.android.launcher3.taskbar; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; + import android.annotation.DrawableRes; -import android.content.Context; import android.view.View; import android.widget.ImageView; import com.android.launcher3.R; +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; /** * Creates Buttons for Taskbar for 3 button nav. @@ -29,47 +34,46 @@ import com.android.launcher3.R; */ public class ButtonProvider { - private int mMarginLeftRight; - private final Context mContext; + private final int mMarginLeftRight; + private final TaskbarActivityContext mContext; - public ButtonProvider(Context context) { + public ButtonProvider(TaskbarActivityContext context) { mContext = context; - } - - public void setMarginLeftRight(int margin) { - mMarginLeftRight = margin; + mMarginLeftRight = context.getResources() + .getDimensionPixelSize(R.dimen.taskbar_icon_spacing); } public View getBack() { // Back button - return getButtonForDrawable(R.drawable.ic_sysbar_back); + return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK); } public View getDown() { // Ime down button - return getButtonForDrawable(R.drawable.ic_sysbar_back); + return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK); } public View getHome() { // Home button - return getButtonForDrawable(R.drawable.ic_sysbar_home); + return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME); } public View getRecents() { // Recents button - return getButtonForDrawable(R.drawable.ic_sysbar_recent); + return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS); } public View getImeSwitcher() { // IME Switcher Button - return getButtonForDrawable(R.drawable.ic_ime_switcher); + return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH); } - private View getButtonForDrawable(@DrawableRes int drawableId) { + private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) { ImageView buttonView = new ImageView(mContext); buttonView.setImageResource(drawableId); buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect); buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0); + buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType)); return buttonView; } diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java index bb3669beed..287caab44b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java @@ -16,9 +16,6 @@ package com.android.launcher3.taskbar; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; - import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -29,7 +26,6 @@ import com.android.launcher3.views.ActivityContext; public class ImeBarView extends RelativeLayout { private ButtonProvider mButtonProvider; - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; private View mImeView; public ImeBarView(Context context) { @@ -44,12 +40,9 @@ public class ImeBarView extends RelativeLayout { super(context, attrs, defStyleAttr); } - public void construct(ButtonProvider buttonProvider) { + public void init(ButtonProvider buttonProvider) { mButtonProvider = buttonProvider; - } - public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) { - mControllerCallbacks = taskbarCallbacks; ActivityContext context = getActivityContext(); RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams( context.getDeviceProfile().iconSizePx, @@ -64,24 +57,16 @@ public class ImeBarView extends RelativeLayout { // Down Arrow View downView = mButtonProvider.getDown(); - downView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_BACK)); downView.setLayoutParams(downParams); downView.setRotation(-90); addView(downView); // IME switcher button mImeView = mButtonProvider.getImeSwitcher(); - mImeView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_IME_SWITCH)); mImeView.setLayoutParams(imeParams); addView(mImeView); } - public void cleanup() { - removeAllViews(); - } - public void setImeSwitcherVisibility(boolean show) { mImeView.setVisibility(show ? VISIBLE : GONE); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 3af51d575d..70f278874a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -15,56 +15,164 @@ */ package com.android.launcher3.taskbar; -import android.content.ContextWrapper; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; +import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; + +import android.app.ActivityOptions; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.SystemProperties; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; +import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; +import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Themes; +import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.WindowManagerWrapper; /** * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements * that are used by both Launcher and Taskbar (such as Folder) to reference a generic * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. */ -public class TaskbarActivityContext extends ContextWrapper implements ActivityContext { +public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext { + + private static final boolean ENABLE_THREE_BUTTON_TASKBAR = + SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); + private static final String TAG = "TaskbarActivityContext"; + + private static final String WINDOW_TITLE = "Taskbar"; private final DeviceProfile mDeviceProfile; private final LayoutInflater mLayoutInflater; private final TaskbarContainerView mTaskbarContainerView; + private final TaskbarIconController mIconController; private final MyDragController mDragController; - public TaskbarActivityContext(BaseQuickstepLauncher launcher) { - super(launcher); - mDeviceProfile = launcher.getDeviceProfile().copy(this); + private final WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + + private final SysUINavigationMode.Mode mNavMode; + private final TaskbarNavButtonController mNavButtonController; + + private final boolean mIsSafeModeEnabled; + + @NonNull + private TaskbarUIController mUIController = TaskbarUIController.DEFAULT; + + private final View.OnClickListener mOnTaskbarIconClickListener; + private final View.OnLongClickListener mOnTaskbarIconLongClickListener; + + public TaskbarActivityContext(Context windowContext, DeviceProfile dp, + TaskbarNavButtonController buttonController) { + super(windowContext, Themes.getActivityThemeRes(windowContext)); + mDeviceProfile = dp; + mNavButtonController = buttonController; + mNavMode = SysUINavigationMode.getMode(windowContext); + mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode", + () -> getPackageManager().isSafeMode()); + + mOnTaskbarIconLongClickListener = + new TaskbarDragController(this)::startSystemDragOnLongClick; + mOnTaskbarIconClickListener = this::onTaskbarIconClicked; + float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size); float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx; mDeviceProfile.updateIconSize(iconScale, getResources()); mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); - mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater .inflate(R.layout.taskbar, null, false); + mIconController = new TaskbarIconController(this, mTaskbarContainerView); mDragController = new MyDragController(this); + + Display display = windowContext.getDisplay(); + Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY + ? windowContext.getApplicationContext() + : windowContext.getApplicationContext().createDisplayContext(display); + mWindowManager = c.getSystemService(WindowManager.class); } - public TaskbarContainerView getTaskbarContainerView() { - return mTaskbarContainerView; + public void init() { + mWindowLayoutParams = new WindowManager.LayoutParams( + MATCH_PARENT, + mDeviceProfile.taskbarSize, + TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.setTitle(WINDOW_TITLE); + mWindowLayoutParams.packageName = getPackageName(); + mWindowLayoutParams.gravity = Gravity.BOTTOM; + mWindowLayoutParams.setFitInsetsTypes(0); + mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWindowLayoutParams.setSystemApplicationOverlay(true); + + WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance(); + wmWrapper.setProvidesInsetsTypes( + mWindowLayoutParams, + new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } + ); + + mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener); + mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams); + } + + /** + * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset). + */ + public void setTaskbarWindowHeight(int height) { + if (mWindowLayoutParams.height == height) { + return; + } + mWindowLayoutParams.height = height; + mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); + } + + public boolean canShowNavButtons() { + return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS; } @Override @@ -73,7 +181,7 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo } @Override - public BaseDragLayer getDragLayer() { + public TaskbarContainerView getDragLayer() { return mTaskbarContainerView; } @@ -92,6 +200,103 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo return mDragController; } + /** + * Sets a new data-source for this taskbar instance + */ + public void setUIController(@NonNull TaskbarUIController uiController) { + mUIController.onDestroy(); + mUIController = uiController; + mIconController.setUIController(mUIController); + mUIController.onCreate(); + } + + /** + * Called when this instance of taskbar is no longer needed + */ + public void onDestroy() { + setUIController(TaskbarUIController.DEFAULT); + mIconController.onDestroy(); + mWindowManager.removeViewImmediate(mTaskbarContainerView); + } + + void onNavigationButtonClick(@TaskbarButton int buttonType) { + mNavButtonController.onButtonClick(buttonType); + } + + public TaskbarIconController getIconController() { + return mIconController; + } + + /** + * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. + */ + protected void setTaskbarWindowFullscreen(boolean fullscreen) { + setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize); + } + + protected void onTaskbarIconClicked(View view) { + Object tag = view.getTag(); + if (tag instanceof Task) { + Task task = (Task) tag; + ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, + ActivityOptions.makeBasic()); + } else if (tag instanceof FolderInfo) { + FolderIcon folderIcon = (FolderIcon) view; + Folder folder = folderIcon.getFolder(); + setTaskbarWindowFullscreen(true); + + getDragLayer().post(() -> { + folder.animateOpen(); + + folder.iterateOverItems((itemInfo, itemView) -> { + itemView.setOnClickListener(mOnTaskbarIconClickListener); + itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener); + // To play haptic when dragging, like other Taskbar items do. + itemView.setHapticFeedbackEnabled(true); + return false; + }); + }); + } else if (tag instanceof WorkspaceItemInfo) { + WorkspaceItemInfo info = (WorkspaceItemInfo) tag; + if (info.isDisabled()) { + ItemClickHandler.handleDisabledItemClicked(info, this); + } else { + Intent intent = new Intent(info.getIntent()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { + Toast.makeText(this, R.string.safemode_shortcut_error, + Toast.LENGTH_SHORT).show(); + } else if (info.isPromise()) { + intent = new PackageManagerHelper(this) + .getMarketIntent(info.getTargetPackage()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + String id = info.getDeepShortcutId(); + String packageName = intent.getPackage(); + getSystemService(LauncherApps.class) + .startShortcut(packageName, id, null, null, info.user); + } else if (info.user.equals(Process.myUserHandle())) { + startActivity(intent); + } else { + getSystemService(LauncherApps.class).startMainActivity( + intent.getComponent(), info.user, intent.getSourceBounds(), null); + } + } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) + .show(); + Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); + } + } + } else { + Log.e(TAG, "Unknown type clicked: " + tag); + } + + AbstractFloatingView.closeAllOpenViews(this); + } + private static class MyDragController extends DragController { MyDragController(TaskbarActivityContext activity) { super(activity); @@ -106,7 +311,8 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo } @Override - protected void exitDrag() { } + protected void exitDrag() { + } @Override protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java index 29f6935f8a..815efb9728 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java @@ -21,6 +21,7 @@ import android.animation.Animator; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Utilities; +import com.android.launcher3.taskbar.TaskbarController.TaskbarAnimationControllerCallbacks; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; @@ -34,7 +35,7 @@ public class TaskbarAnimationController { private static final long IME_VISIBILITY_ALPHA_DURATION = 120; private final BaseQuickstepLauncher mLauncher; - private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks; + private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks; // Background alpha. private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat( @@ -55,7 +56,7 @@ public class TaskbarAnimationController { this::updateTranslationY); public TaskbarAnimationController(BaseQuickstepLauncher launcher, - TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) { + TaskbarAnimationControllerCallbacks taskbarCallbacks) { mLauncher = launcher; mTaskbarCallbacks = taskbarCallbacks; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java index 621bba7e85..5034791f0e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java @@ -15,9 +15,6 @@ */ package com.android.launcher3.taskbar; -import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME; -import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; - import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; @@ -32,22 +29,21 @@ import com.android.launcher3.R; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; import com.android.systemui.shared.system.ViewTreeObserverWrapper; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener; /** * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder. */ public class TaskbarContainerView extends BaseDragLayer { - private final int[] mTempLoc = new int[2]; private final int mFolderMargin; private final Paint mTaskbarBackgroundPaint; - // Initialized in TaskbarController constructor. - private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks; - - // Initialized in init. + private TaskbarIconController.Callbacks mControllerCallbacks; private TaskbarView mTaskbarView; - private ViewTreeObserverWrapper.OnComputeInsetsListener mTaskbarInsetsComputer; + + private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; public TaskbarContainerView(@NonNull Context context) { this(context, null); @@ -68,15 +64,6 @@ public class TaskbarContainerView extends BaseDragLayer mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin); mTaskbarBackgroundPaint = new Paint(); mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background)); - } - - protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) { - mControllerCallbacks = callbacks; - } - - protected void init(TaskbarView taskbarView) { - mTaskbarView = taskbarView; - mTaskbarInsetsComputer = createTaskbarInsetsComputer(); recreateControllers(); } @@ -85,46 +72,24 @@ public class TaskbarContainerView extends BaseDragLayer mControllers = new TouchController[0]; } - private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() { - return insetsInfo -> { - if (mControllerCallbacks.isTaskbarTouchable()) { - // Accept touches anywhere in our bounds. - insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); - } else { - // Let touches pass through us. - insetsInfo.touchableRegion.setEmpty(); - insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); - } - - // TaskbarContainerView provides insets to other apps based on contentInsets. These - // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. - // to show a floating view like Folder. Thus, we set the contentInsets to be where - // mTaskbarView is, since its position never changes and insets rather than overlays. - int[] loc = mTempLoc; - float scale = mTaskbarView.getScaleX(); - float translationY = mTaskbarView.getTranslationY(); - mTaskbarView.setScaleX(1); - mTaskbarView.setScaleY(1); - mTaskbarView.setTranslationY(0); - mTaskbarView.getLocationInWindow(loc); - mTaskbarView.setScaleX(scale); - mTaskbarView.setScaleY(scale); - mTaskbarView.setTranslationY(translationY); - insetsInfo.contentInsets.left = loc[0]; - insetsInfo.contentInsets.top = loc[1]; - insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth()); - insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight()); - }; + public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) { + mControllerCallbacks = callbacks; + mTaskbarView = taskbarView; } - protected void cleanup() { + private void onComputeTaskbarInsets(InsetsInfo insetsInfo) { + if (mControllerCallbacks != null) { + mControllerCallbacks.updateInsetsTouchability(insetsInfo); + } + } + + protected void onDestroy() { ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(), mTaskbarInsetsComputer); } @@ -133,7 +98,7 @@ public class TaskbarContainerView extends BaseDragLayer protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - cleanup(); + onDestroy(); } @Override @@ -143,10 +108,25 @@ public class TaskbarContainerView extends BaseDragLayer return true; } + public void updateImeBarVisibilityAlpha(float alpha) { + if (mControllerCallbacks != null) { + mControllerCallbacks.updateImeBarVisibilityAlpha(alpha); + } + } + @Override public void onViewRemoved(View child) { super.onViewRemoved(child); - mControllerCallbacks.onViewRemoved(); + if (mControllerCallbacks != null) { + mControllerCallbacks.onContainerViewRemoved(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(), + canvas.getHeight(), mTaskbarBackgroundPaint); + super.dispatchDraw(canvas); } /** @@ -158,16 +138,6 @@ public class TaskbarContainerView extends BaseDragLayer return boundingBox; } - protected TaskbarActivityContext getTaskbarActivityContext() { - return mActivity; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(), - canvas.getHeight(), mTaskbarBackgroundPaint); - super.dispatchDraw(canvas); - } /** * Sets the alpha of the background color behind all the Taskbar contents. diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index 6084e10d85..cdae5beb9a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -15,105 +15,83 @@ */ package com.android.launcher3.taskbar; -import static android.view.View.GONE; -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; -import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.app.ActivityOptions; -import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.Rect; -import android.inputmethodservice.InputMethodService; -import android.view.Gravity; +import android.graphics.RectF; import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; import androidx.annotation.Nullable; -import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; -import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.model.data.FolderInfo; -import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; -import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; -import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.views.ActivityContext; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.TouchInteractionService.TaskbarOverviewProxyDelegate; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.WindowManagerWrapper; + /** - * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView. + * A data source which integrates with a Launcher instance + * TODO: Rename to have Launcher prefix */ -public class TaskbarController implements TaskbarOverviewProxyDelegate { - private static final String WINDOW_TITLE = "Taskbar"; - - private final TaskbarContainerView mTaskbarContainerView; - private final TaskbarView mTaskbarViewInApp; - private final TaskbarView mTaskbarViewOnHome; - private final ImeBarView mImeBarView; +public class TaskbarController extends TaskbarUIController { private final BaseQuickstepLauncher mLauncher; - private final WindowManager mWindowManager; - // Layout width and height of the Taskbar in the default state. - private final Point mTaskbarSize; private final TaskbarStateHandler mTaskbarStateHandler; private final TaskbarAnimationController mTaskbarAnimationController; private final TaskbarHotseatController mHotseatController; - private final TaskbarDragController mDragController; - private final TaskbarNavButtonController mNavButtonController; - // Initialized in init(). - private WindowManager.LayoutParams mWindowLayoutParams; - private SysUINavigationMode.Mode mNavMode = SysUINavigationMode.Mode.NO_BUTTON; - private final SysUINavigationMode.NavigationModeChangeListener mNavigationModeChangeListener = - this::onNavModeChanged; + private final TaskbarActivityContext mContext; + final TaskbarContainerView mTaskbarContainerView; + final TaskbarView mTaskbarView; private @Nullable Animator mAnimator; private boolean mIsAnimatingToLauncher; - public TaskbarController(BaseQuickstepLauncher launcher, - TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) { + public TaskbarController(BaseQuickstepLauncher launcher, TaskbarActivityContext context) { + mContext = context; + mTaskbarContainerView = context.getDragLayer(); + mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); + mLauncher = launcher; - mTaskbarContainerView = taskbarContainerView; - mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks()); - ButtonProvider buttonProvider = new ButtonProvider(launcher); - mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view); - mTaskbarViewInApp.construct(createTaskbarViewCallbacks(), buttonProvider); - mTaskbarViewOnHome = taskbarViewOnHome; - mTaskbarViewOnHome.construct(createTaskbarViewCallbacks(), buttonProvider); - mImeBarView = mTaskbarContainerView.findViewById(R.id.ime_bar_view); - mImeBarView.construct(buttonProvider); - mNavButtonController = new TaskbarNavButtonController(launcher); - mWindowManager = mLauncher.getWindowManager(); - mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize); mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); mTaskbarAnimationController = new TaskbarAnimationController(mLauncher, createTaskbarAnimationControllerCallbacks()); - mHotseatController = new TaskbarHotseatController(mLauncher, - createTaskbarHotseatControllerCallbacks()); - mDragController = new TaskbarDragController(mLauncher); + mHotseatController = new TaskbarHotseatController( + mLauncher, mTaskbarView::updateHotseatItems); + } + + @Override + protected void onCreate() { + mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController); + mTaskbarAnimationController.init(); + mHotseatController.init(); + setTaskbarViewVisible(!mLauncher.hasBeenResumed()); + alignRealHotseatWithTaskbar(); + mLauncher.setTaskbarController(this); + } + + @Override + protected void onDestroy() { + if (mAnimator != null) { + // End this first, in case it relies on properties that are about to be cleaned up. + mAnimator.end(); + } + mTaskbarStateHandler.setAnimationController(null); + mTaskbarAnimationController.cleanup(); + mHotseatController.cleanup(); + setTaskbarViewVisible(true); + mLauncher.getHotseat().setIconsAlpha(1f); + mLauncher.setTaskbarController(null); + } + + @Override + protected boolean isTaskbarTouchable() { + return !mIsAnimatingToLauncher; } private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() { @@ -125,245 +103,34 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { @Override public void updateTaskbarVisibilityAlpha(float alpha) { - mTaskbarViewInApp.setAlpha(alpha); - mTaskbarViewOnHome.setAlpha(alpha); + mTaskbarView.setAlpha(alpha); } @Override public void updateImeBarVisibilityAlpha(float alpha) { - if (mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { - // TODO Remove sysui IME bar for gesture nav as well - return; - } - mImeBarView.setAlpha(alpha); - mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE); + mTaskbarContainerView.updateImeBarVisibilityAlpha(alpha); } @Override public void updateTaskbarScale(float scale) { - mTaskbarViewInApp.setScaleX(scale); - mTaskbarViewInApp.setScaleY(scale); + mTaskbarView.setScaleX(scale); + mTaskbarView.setScaleY(scale); } @Override public void updateTaskbarTranslationY(float translationY) { if (translationY < 0) { // Resize to accommodate the max translation we'll reach. - setTaskbarWindowHeight(mTaskbarSize.y + mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize + mLauncher.getHotseat().getTaskbarOffsetY()); } else { - setTaskbarWindowHeight(mTaskbarSize.y); + mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize); } - mTaskbarViewInApp.setTranslationY(translationY); + mTaskbarView.setTranslationY(translationY); } }; } - private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() { - return new TaskbarContainerViewCallbacks() { - @Override - public void onViewRemoved() { - // Ensure no other children present (like Folders, etc) - for (int i = 0; i < mTaskbarContainerView.getChildCount(); i++) { - View v = mTaskbarContainerView.getChildAt(i); - if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))){ - return; - } - } - setTaskbarWindowFullscreen(false); - } - - @Override - public boolean isTaskbarTouchable() { - return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD - && (mTaskbarViewInApp.getVisibility() == VISIBLE - || mImeBarView.getVisibility() == VISIBLE) - && !mIsAnimatingToLauncher; - } - }; - } - - private TaskbarViewCallbacks createTaskbarViewCallbacks() { - return new TaskbarViewCallbacks() { - @Override - public View.OnClickListener getItemOnClickListener() { - return view -> { - Object tag = view.getTag(); - if (tag instanceof Task) { - Task task = (Task) tag; - ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, - ActivityOptions.makeBasic()); - } else if (tag instanceof FolderInfo) { - FolderIcon folderIcon = (FolderIcon) view; - Folder folder = folderIcon.getFolder(); - - setTaskbarWindowFullscreen(true); - - mTaskbarContainerView.post(() -> { - folder.animateOpen(); - - folder.iterateOverItems((itemInfo, itemView) -> { - itemView.setOnClickListener(getItemOnClickListener()); - itemView.setOnLongClickListener(getItemOnLongClickListener()); - // To play haptic when dragging, like other Taskbar items do. - itemView.setHapticFeedbackEnabled(true); - return false; - }); - }); - } else { - ItemClickHandler.INSTANCE.onClick(view); - } - - AbstractFloatingView.closeAllOpenViews( - mTaskbarContainerView.getTaskbarActivityContext()); - }; - } - - @Override - public View.OnLongClickListener getItemOnLongClickListener() { - return mDragController::startSystemDragOnLongClick; - } - - @Override - public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) { - // When on the home screen, we want the empty hotseat views to take up their full - // space so that the others line up with the home screen hotseat. - boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome - || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher; - return isOnHomeScreen ? INVISIBLE : GONE; - } - - @Override - public float getNonIconScale(TaskbarView taskbarView) { - return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f; - } - - @Override - public void onItemPositionsChanged(TaskbarView taskbarView) { - if (taskbarView == mTaskbarViewOnHome) { - alignRealHotseatWithTaskbar(); - } - } - - @Override - public void onNavigationButtonClick(@TaskbarButton int buttonType) { - mNavButtonController.onButtonClick(buttonType); - } - }; - } - - private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() { - return new TaskbarHotseatControllerCallbacks() { - @Override - public void updateHotseatItems(ItemInfo[] hotseatItemInfos) { - mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos); - } - }; - } - - /** - * Initializes the Taskbar, including adding it to the screen. - */ - public void init() { - mNavMode = SysUINavigationMode.INSTANCE.get(mLauncher) - .addModeChangeListener(mNavigationModeChangeListener); - mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(), mNavMode); - mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(), mNavMode); - mTaskbarContainerView.init(mTaskbarViewInApp); - mImeBarView.init(createTaskbarViewCallbacks()); - addToWindowManager(); - mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks()); - mTaskbarAnimationController.init(); - mHotseatController.init(); - - setWhichTaskbarViewIsVisible(mLauncher.hasBeenResumed() - ? mTaskbarViewOnHome - : mTaskbarViewInApp); - } - - private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() { - return new TaskbarStateHandlerCallbacks() { - @Override - public AnimatedFloat getAlphaTarget() { - return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState(); - } - - @Override - public AnimatedFloat getScaleTarget() { - return mTaskbarAnimationController.getTaskbarScaleForLauncherState(); - } - - @Override - public AnimatedFloat getTranslationYTarget() { - return mTaskbarAnimationController.getTaskbarTranslationYForLauncherState(); - } - }; - } - - /** - * Removes the Taskbar from the screen, and removes any obsolete listeners etc. - */ - public void cleanup() { - if (mAnimator != null) { - // End this first, in case it relies on properties that are about to be cleaned up. - mAnimator.end(); - } - - mTaskbarViewInApp.cleanup(); - mTaskbarViewOnHome.cleanup(); - mTaskbarContainerView.cleanup(); - mImeBarView.cleanup(); - removeFromWindowManager(); - mTaskbarStateHandler.setTaskbarCallbacks(null); - mTaskbarAnimationController.cleanup(); - mHotseatController.cleanup(); - - setWhichTaskbarViewIsVisible(null); - SysUINavigationMode.INSTANCE.get(mLauncher) - .removeModeChangeListener(mNavigationModeChangeListener); - } - - private void removeFromWindowManager() { - mWindowManager.removeViewImmediate(mTaskbarContainerView); - } - - private void addToWindowManager() { - final int gravity = Gravity.BOTTOM; - - mWindowLayoutParams = new WindowManager.LayoutParams( - mTaskbarSize.x, - mTaskbarSize.y, - TYPE_APPLICATION_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - mWindowLayoutParams.setTitle(WINDOW_TITLE); - mWindowLayoutParams.packageName = mLauncher.getPackageName(); - mWindowLayoutParams.gravity = gravity; - mWindowLayoutParams.setFitInsetsTypes(0); - mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setSystemApplicationOverlay(true); - - WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance(); - wmWrapper.setProvidesInsetsTypes( - mWindowLayoutParams, - new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } - ); - - TaskbarContainerView.LayoutParams taskbarLayoutParams = - new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y); - taskbarLayoutParams.gravity = gravity; - mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams); - - mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams); - } - - private void onNavModeChanged(SysUINavigationMode.Mode newMode) { - mNavMode = newMode; - cleanup(); - init(); - } - /** * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. */ @@ -401,13 +168,14 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { @Override public void onAnimationStart(Animator animation) { mIsAnimatingToLauncher = true; - mTaskbarViewInApp.updateHotseatItemsVisibility(); + mTaskbarView.setHolesAllowedInLayout(true); + mTaskbarView.updateHotseatItemsVisibility(); } @Override public void onAnimationEnd(Animator animation) { mIsAnimatingToLauncher = false; - setWhichTaskbarViewIsVisible(mTaskbarViewOnHome); + setTaskbarViewVisible(false); } }); @@ -420,44 +188,21 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - mTaskbarViewInApp.updateHotseatItemsVisibility(); - setWhichTaskbarViewIsVisible(mTaskbarViewInApp); + mTaskbarView.updateHotseatItemsVisibility(); + setTaskbarViewVisible(true); } @Override public void onAnimationEnd(Animator animation) { + mTaskbarView.setHolesAllowedInLayout(false); } }); return anim.buildAnim(); } - /** - * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly. - */ - public void setIsImeVisible(boolean isImeVisible) { - mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1); - blockTaskbarTouchesForIme(isImeVisible); - } - - /** - * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from - * instantiating at all, which is what's responsible for sending sysui state flags over. - * - * @param vis IME visibility flag - * @param backDisposition Used to determine back button behavior for software keyboard - * See BACK_DISPOSITION_* constants in {@link InputMethodService} - */ - public void updateImeStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { - if (displayId != mTaskbarContainerView.getContext().getDisplayId() || - mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { - return; - } - - boolean imeVisible = (vis & InputMethodService.IME_VISIBLE) != 0; - mTaskbarAnimationController.animateToVisibilityForIme(imeVisible ? 0 : 1); - mImeBarView.setImeSwitcherVisibility(showImeSwitcher); - blockTaskbarTouchesForIme(imeVisible); + @Override + protected void onImeVisible(TaskbarContainerView containerView, boolean isVisible) { + mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1); } /** @@ -472,24 +217,17 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { * @return Whether any Taskbar item could handle the given MotionEvent if given the chance. */ public boolean isEventOverAnyTaskbarItem(MotionEvent ev) { - return mTaskbarViewInApp.isEventOverAnyItem(ev); + return mTaskbarView.isEventOverAnyItem(ev); } public boolean isDraggingItem() { - return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem(); - } - - /** - * @return Whether the given View is in the same window as Taskbar. - */ - public boolean isViewInTaskbar(View v) { - return mTaskbarContainerView.isAttachedToWindow() - && mTaskbarContainerView.getWindowId().equals(v.getWindowId()); + return mTaskbarView.isDraggingItem(); } /** * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat. */ + @Override public void alignRealHotseatWithTaskbar() { Rect hotseatBounds = new Rect(); DeviceProfile grid = mLauncher.getDeviceProfile(); @@ -498,60 +236,28 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset; int hotseatBottomDiff = taskbarOffset; - mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds); + RectF hotseatBoundsF = mTaskbarView.getHotseatBounds(); + Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(), + mTaskbarView.getPivotX(), mTaskbarView.getPivotY()); + hotseatBoundsF.round(hotseatBounds); mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top + hotseatTopDiff, - mTaskbarViewOnHome.getWidth() - hotseatBounds.right, - mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom + hotseatBottomDiff); - } - - private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) { - mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp - ? VISIBLE : INVISIBLE); - mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome - ? VISIBLE : INVISIBLE); - mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f); - } - - private void blockTaskbarTouchesForIme(boolean block) { - mTaskbarViewOnHome.setTouchesEnabled(!block); - mTaskbarViewInApp.setTouchesEnabled(!block); + mTaskbarView.getWidth() - hotseatBounds.right, + mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff); } /** * Returns the ratio of the taskbar icon size on home vs in an app. */ public float getTaskbarScaleOnHome() { - DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext() - .getDeviceProfile(); - DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext()) - .getDeviceProfile(); + DeviceProfile inAppDp = mContext.getDeviceProfile(); + DeviceProfile onHomeDp = mLauncher.getDeviceProfile(); return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx; } - /** - * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. - */ - private void setTaskbarWindowFullscreen(boolean fullscreen) { - setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mTaskbarSize.y); - } - - /** - * Updates the TaskbarContainer height (pass mTaskbarSize.y to reset). - */ - private void setTaskbarWindowHeight(int height) { - mWindowLayoutParams.width = mTaskbarSize.x; - mWindowLayoutParams.height = height; - mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); - } - - /** - * Contains methods that TaskbarStateHandler can call to interface with TaskbarController. - */ - protected interface TaskbarStateHandlerCallbacks { - AnimatedFloat getAlphaTarget(); - AnimatedFloat getScaleTarget(); - AnimatedFloat getTranslationYTarget(); + void setTaskbarViewVisible(boolean isVisible) { + mTaskbarView.setIconsVisibility(isVisible); + mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f); } /** @@ -565,32 +271,4 @@ public class TaskbarController implements TaskbarOverviewProxyDelegate { void updateTaskbarScale(float scale); void updateTaskbarTranslationY(float translationY); } - - /** - * Contains methods that TaskbarContainerView can call to interface with TaskbarController. - */ - protected interface TaskbarContainerViewCallbacks { - void onViewRemoved(); - boolean isTaskbarTouchable(); - } - - /** - * Contains methods that TaskbarView can call to interface with TaskbarController. - */ - protected interface TaskbarViewCallbacks { - View.OnClickListener getItemOnClickListener(); - View.OnLongClickListener getItemOnLongClickListener(); - int getEmptyHotseatViewVisibility(TaskbarView taskbarView); - /** Returns how much to scale non-icon elements such as spacing and dividers. */ - float getNonIconScale(TaskbarView taskbarView); - void onItemPositionsChanged(TaskbarView taskbarView); - void onNavigationButtonClick(@TaskbarButton int buttonType); - } - - /** - * Contains methods that TaskbarHotseatController can call to interface with TaskbarController. - */ - protected interface TaskbarHotseatControllerCallbacks { - void updateHotseatItems(ItemInfo[] hotseatItemInfos); - } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index 5eb34cb360..ee44927ce4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -20,6 +20,7 @@ import static android.view.View.VISIBLE; import android.content.ClipData; import android.content.ClipDescription; +import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.res.Resources; @@ -29,7 +30,6 @@ import android.os.UserHandle; import android.view.DragEvent; import android.view.View; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -43,12 +43,12 @@ import com.android.systemui.shared.system.LauncherAppsCompat; */ public class TaskbarDragController { - private final BaseQuickstepLauncher mLauncher; + private final Context mContext; private final int mDragIconSize; - public TaskbarDragController(BaseQuickstepLauncher launcher) { - mLauncher = launcher; - Resources resources = mLauncher.getResources(); + public TaskbarDragController(Context context) { + mContext = context; + Resources resources = mContext.getResources(); mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size); } @@ -63,7 +63,6 @@ public class TaskbarDragController { } BubbleTextView btv = (BubbleTextView) view; - View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) { @Override public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { @@ -87,7 +86,7 @@ public class TaskbarDragController { Intent intent = null; if (tag instanceof WorkspaceItemInfo) { WorkspaceItemInfo item = (WorkspaceItemInfo) tag; - LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class); + LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); clipDescription = new ClipDescription(item.title, new String[] { item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java index 68829cd0fb..91cf7efab5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java @@ -26,6 +26,8 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.model.data.ItemInfo; +import java.util.function.Consumer; + /** * Works with TaskbarController to update the TaskbarView's Hotseat items. */ @@ -33,13 +35,12 @@ public class TaskbarHotseatController { private final BaseQuickstepLauncher mLauncher; private final Hotseat mHotseat; - private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks; + private final Consumer mTaskbarCallbacks; private final int mNumHotseatIcons; private final DragController.DragListener mDragListener = new DragController.DragListener() { @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - } + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } @Override public void onDragEnd() { @@ -47,8 +48,8 @@ public class TaskbarHotseatController { } }; - public TaskbarHotseatController(BaseQuickstepLauncher launcher, - TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) { + public TaskbarHotseatController( + BaseQuickstepLauncher launcher, Consumer taskbarCallbacks) { mLauncher = launcher; mHotseat = mLauncher.getHotseat(); mTaskbarCallbacks = taskbarCallbacks; @@ -85,10 +86,6 @@ public class TaskbarHotseatController { } } - mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos); - } - - protected int getNumHotseatIcons() { - return mNumHotseatIcons; + mTaskbarCallbacks.accept(hotseatItemInfos); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java new file mode 100644 index 0000000000..2a37915fcf --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME; +import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; + +import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; + +import androidx.annotation.NonNull; + +import com.android.launcher3.R; +import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; + +/** + * Controller for taskbar icon UI + */ +public class TaskbarIconController { + + private final Rect mTempRect = new Rect(); + + private final TaskbarActivityContext mActivity; + private final TaskbarContainerView mContainerView; + + private final TaskbarView mTaskbarView; + private final ImeBarView mImeBarView; + + @NonNull + private TaskbarUIController mUIController = TaskbarUIController.DEFAULT; + + TaskbarIconController(TaskbarActivityContext activity, TaskbarContainerView containerView) { + mActivity = activity; + mContainerView = containerView; + mTaskbarView = mContainerView.findViewById(R.id.taskbar_view); + mImeBarView = mContainerView.findViewById(R.id.ime_bar_view); + } + + public void init(OnClickListener clickListener, OnLongClickListener longClickListener) { + mContainerView.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) -> + mUIController.alignRealHotseatWithTaskbar()); + + ButtonProvider buttonProvider = new ButtonProvider(mActivity); + mImeBarView.init(buttonProvider); + mTaskbarView.construct(clickListener, longClickListener, buttonProvider); + mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + mContainerView.init(new Callbacks(), mTaskbarView); + } + + public void onDestroy() { + mContainerView.onDestroy(); + } + + public void setUIController(@NonNull TaskbarUIController uiController) { + mUIController = uiController; + } + + /** + * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from + * instantiating at all, which is what's responsible for sending sysui state flags over. + * + * @param vis IME visibility flag + */ + public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) { + if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) { + return; + } + + mImeBarView.setImeSwitcherVisibility(showImeSwitcher); + setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0); + } + + /** + * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly. + */ + public void setImeIsVisible(boolean isImeVisible) { + mTaskbarView.setTouchesEnabled(!isImeVisible); + mUIController.onImeVisible(mContainerView, isImeVisible); + } + + /** + * Callbacks for {@link TaskbarContainerView} to interact with the icon controller + */ + public class Callbacks { + + /** + * Called to update the touchable insets + */ + public void updateInsetsTouchability(InsetsInfo insetsInfo) { + insetsInfo.touchableRegion.setEmpty(); + if (mContainerView.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) { + // Let touches pass through us. + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } else if (mImeBarView.getVisibility() == VISIBLE) { + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); + } else if (!mUIController.isTaskbarTouchable()) { + // Let touches pass through us. + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } else if (mTaskbarView.areIconsVisible()) { + // Buttons are visible, take over the full taskbar area + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); + } else { + if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) { + mContainerView.getDescendantRectRelativeToSelf( + mTaskbarView.mSystemButtonContainer, mTempRect); + insetsInfo.touchableRegion.set(mTempRect); + } + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } + + // TaskbarContainerView provides insets to other apps based on contentInsets. These + // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. + // to show a floating view like Folder. Thus, we set the contentInsets to be where + // mTaskbarView is, since its position never changes and insets rather than overlays. + insetsInfo.contentInsets.left = mTaskbarView.getLeft(); + insetsInfo.contentInsets.top = mTaskbarView.getTop(); + insetsInfo.contentInsets.right = mContainerView.getWidth() - mTaskbarView.getRight(); + insetsInfo.contentInsets.bottom = mContainerView.getHeight() - mTaskbarView.getBottom(); + } + + public void onContainerViewRemoved() { + int count = mContainerView.getChildCount(); + // Ensure no other children present (like Folders, etc) + for (int i = 0; i < count; i++) { + View v = mContainerView.getChildAt(i); + if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) { + return; + } + } + mActivity.setTaskbarWindowFullscreen(false); + } + + public void updateImeBarVisibilityAlpha(float alpha) { + if (!mActivity.canShowNavButtons()) { + // TODO Remove sysui IME bar for gesture nav as well + return; + } + mImeBarView.setAlpha(alpha); + mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE); + } + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java new file mode 100644 index 0000000000..b9eec93687 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; +import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; +import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.inputmethodservice.InputMethodService; +import android.view.Display; + +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.DisplayController.Info; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.TouchInteractionService; + +/** + * Class to manager taskbar lifecycle + */ +public class TaskbarManager implements DisplayController.DisplayInfoChangeListener, + SysUINavigationMode.NavigationModeChangeListener { + + private final Context mContext; + private final DisplayController mDisplayController; + private final SysUINavigationMode mSysUINavigationMode; + private final TaskbarNavButtonController mNavButtonController; + + private TaskbarActivityContext mTaskbarActivityContext; + private BaseQuickstepLauncher mLauncher; + + private static final int CHANGE_FLAGS = + CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS; + + public TaskbarManager(TouchInteractionService service) { + mDisplayController = DisplayController.INSTANCE.get(service); + mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service); + Display display = + service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY); + mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null); + mNavButtonController = new TaskbarNavButtonController(service); + + mDisplayController.addChangeListener(this); + mSysUINavigationMode.addModeChangeListener(this); + recreateTaskbar(); + } + + @Override + public void onNavigationModeChanged(Mode newMode) { + recreateTaskbar(); + } + + @Override + public void onDisplayInfoChanged(Context context, Info info, int flags) { + if ((flags & CHANGE_FLAGS) != 0) { + recreateTaskbar(); + } + } + + private void destroyExistingTaskbar() { + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.onDestroy(); + mTaskbarActivityContext = null; + } + } + + /** + * Sets or clears a launcher to act as taskbar callback + */ + public void setLauncher(@Nullable BaseQuickstepLauncher launcher) { + mLauncher = launcher; + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.setUIController(mLauncher == null + ? TaskbarUIController.DEFAULT + : new TaskbarController(launcher, mTaskbarActivityContext)); + } + } + + private void recreateTaskbar() { + destroyExistingTaskbar(); + if (!FeatureFlags.ENABLE_TASKBAR.get()) { + return; + } + DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext); + if (!dp.isTaskbarPresent) { + return; + } + mTaskbarActivityContext = new TaskbarActivityContext( + mContext, dp.copy(mContext), mNavButtonController); + mTaskbarActivityContext.init(); + if (mLauncher != null) { + mTaskbarActivityContext.setUIController( + new TaskbarController(mLauncher, mTaskbarActivityContext)); + } + } + + /** + * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags} + * @param systemUiStateFlags The latest SystemUiStateFlags + */ + public void onSystemUiFlagsChanged(int systemUiStateFlags) { + boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0; + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.getIconController().setImeIsVisible(isImeVisible); + } + } + + /** + * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from + * instantiating at all, which is what's responsible for sending sysui state flags over. + * + * @param vis IME visibility flag + * @param backDisposition Used to determine back button behavior for software keyboard + * See BACK_DISPOSITION_* constants in {@link InputMethodService} + */ + public void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher) { + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.getIconController() + .updateImeStatus(displayId, vis, showImeSwitcher); + } + } + + /** + * Called when the manager is no longer needed + */ + public void destroy() { + destroyExistingTaskbar(); + mDisplayController.removeChangeListener(this); + mSysUINavigationMode.removeModeChangeListener(this); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index 54e1610cd1..3b5afad691 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -16,7 +16,8 @@ package com.android.launcher3.taskbar; -import android.content.Context; +import static android.view.Display.DEFAULT_DISPLAY; + import android.content.Intent; import android.view.inputmethod.InputMethodManager; @@ -53,11 +54,10 @@ public class TaskbarNavButtonController { static final int BUTTON_RECENTS = BUTTON_HOME << 1; static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; + private final TouchInteractionService mService; - private final Context mContext; - - public TaskbarNavButtonController(Context context) { - mContext = context; + public TaskbarNavButtonController(TouchInteractionService service) { + mService = service; } public void onButtonClick(@TaskbarButton int buttonType) { @@ -78,13 +78,13 @@ public class TaskbarNavButtonController { } private void navigateHome() { - mContext.startActivity(new Intent(Intent.ACTION_MAIN) + mService.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } private void navigateToOverview() { - TouchInteractionService.getInstance().getOverviewCommandHelper() + mService.getOverviewCommandHelper() .addCommand(OverviewCommandHelper.TYPE_SHOW); } @@ -93,8 +93,8 @@ public class TaskbarNavButtonController { } private void showIMESwitcher() { - mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem( - true /* showAuxiliarySubtypes */, mContext.getDisplayId()); + mService.getSystemService(InputMethodManager.class) + .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, + DEFAULT_DISPLAY); } - } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java index 6ea51fa5eb..a701aae088 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java @@ -24,59 +24,52 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.quickstep.AnimatedFloat; /** * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar - * isn't present (i.e. {@link #setTaskbarCallbacks} is never called). + * isn't present (i.e. {@link #setAnimationController} is never called). */ public class TaskbarStateHandler implements StateManager.StateHandler { private final BaseQuickstepLauncher mLauncher; // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything. - private @Nullable TaskbarController.TaskbarStateHandlerCallbacks mTaskbarCallbacks = null; + private @Nullable TaskbarAnimationController mAnimationController = null; public TaskbarStateHandler(BaseQuickstepLauncher launcher) { mLauncher = launcher; } - public void setTaskbarCallbacks(TaskbarController.TaskbarStateHandlerCallbacks callbacks) { - mTaskbarCallbacks = callbacks; + public void setAnimationController(TaskbarAnimationController callbacks) { + mAnimationController = callbacks; } @Override public void setState(LauncherState state) { - if (mTaskbarCallbacks == null) { - return; - } - - AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget(); - AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget(); - AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget(); - boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0; - alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f); - scaleTarget.updateValue(state.getTaskbarScale(mLauncher)); - translationYTarget.updateValue(state.getTaskbarTranslationY(mLauncher)); + setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER); } @Override public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation) { - if (mTaskbarCallbacks == null) { + setState(toState, animation); + } + + private void setState(LauncherState toState, PropertySetter setter) { + if (mAnimationController == null) { return; } - AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget(); - AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget(); - AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget(); boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0; - animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR); - animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), - LINEAR); - animation.setFloat(translationYTarget, AnimatedFloat.VALUE, - toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL); + setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(), + AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR); + setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(), + AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR); + setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(), + AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java new file mode 100644 index 0000000000..e16f5e65b0 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +/** + * Base class for providing different taskbar UI + */ +public class TaskbarUIController { + + public static final TaskbarUIController DEFAULT = new TaskbarUIController(); + + /** + * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat. + */ + public void alignRealHotseatWithTaskbar() { } + + protected void onCreate() { } + + protected void onDestroy() { } + + protected boolean isTaskbarTouchable() { + return true; + } + + protected void onImeVisible(TaskbarContainerView container, boolean isVisible) { + container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 9e8013e63e..c6573a639c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -15,20 +15,14 @@ */ package com.android.launcher3.taskbar; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.LayoutTransition; -import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; -import android.os.SystemProperties; import android.util.AttributeSet; import android.view.DragEvent; import android.view.Gravity; @@ -51,17 +45,12 @@ import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.views.ActivityContext; -import com.android.quickstep.SysUINavigationMode; /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable { - - private static final boolean ENABLE_THREE_BUTTON_TASKBAR = - SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); - private final int mIconTouchSize; private final boolean mIsRtl; private final int mTouchSlop; @@ -69,17 +58,16 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa private final RectF mDelegateSlopBounds = new RectF(); private final int[] mTempOutLocation = new int[2]; - // Initialized in TaskbarController constructor. - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; - // Scale on elements that aren't icons. - private float mNonIconScale; - private int mItemMarginLeftRight; + private final int mItemMarginLeftRight; - // Initialized in init(). - private LayoutTransition mLayoutTransition; - private int mHotseatStartIndex; - private int mHotseatEndIndex; - private LinearLayout mButtonRegion; + private final TaskbarActivityContext mActivityContext; + + // Initialized in TaskbarController constructor. + private View.OnClickListener mIconClickListener; + private View.OnLongClickListener mIconLongClickListener; + + LinearLayout mSystemButtonContainer; + LinearLayout mHotseatIconsContainer; // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; @@ -91,10 +79,12 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa // Only non-null when the corresponding Folder is open. private @Nullable FolderIcon mLeaveBehindFolderIcon; - private int mNavButtonStartIndex; /** Provider of buttons added to taskbar in 3 button nav */ private ButtonProvider mButtonProvider; + private boolean mDisableRelayout; + private boolean mAreHolesAllowed; + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -111,80 +101,58 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mActivityContext = ActivityContext.lookupContext(context); Resources resources = getResources(); mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size); + mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing); + mIsRtl = Utilities.isRtl(resources); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } - protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks, - ButtonProvider buttonProvider) { - mControllerCallbacks = taskbarViewCallbacks; - mNonIconScale = mControllerCallbacks.getNonIconScale(this); - mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing); - mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale); - mButtonProvider = buttonProvider; - mButtonProvider.setMarginLeftRight(mItemMarginLeftRight); + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSystemButtonContainer = findViewById(R.id.system_button_layout); + mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout); } - protected void init(int numHotseatIcons, SysUINavigationMode.Mode newMode) { - // TODO: check if buttons on left - if (newMode == SysUINavigationMode.Mode.THREE_BUTTONS && ENABLE_THREE_BUTTON_TASKBAR) { - // 3 button - mNavButtonStartIndex = 0; + protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener, + ButtonProvider buttonProvider) { + mIconClickListener = clickListener; + mIconLongClickListener = longClickListener; + mButtonProvider = buttonProvider; + + if (mActivityContext.canShowNavButtons()) { createNavButtons(); } else { - mNavButtonStartIndex = -1; - removeNavButtons(); + mSystemButtonContainer.setVisibility(GONE); } - mHotseatStartIndex = mNavButtonStartIndex + 1; - mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; + int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons; updateHotseatItems(new ItemInfo[numHotseatIcons]); - - mLayoutTransition = new LayoutTransition(); - addUpdateListenerForAllLayoutTransitions(() -> { - if (getLayoutTransition() == mLayoutTransition) { - mControllerCallbacks.onItemPositionsChanged(this); - } - }); - setLayoutTransition(mLayoutTransition); } - private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) { - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate); - } - - private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) { - Animator anim = mLayoutTransition.getAnimator(transitionType); - if (anim instanceof ValueAnimator) { - ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run()); - } else { - AnimatorSet animSet = new AnimatorSet(); - ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1); - updateAnim.addUpdateListener(valueAnimator -> onUpdate.run()); - animSet.playTogether(anim, updateAnim); - mLayoutTransition.setAnimator(transitionType, animSet); + /** + * Enables/disables empty icons in taskbar so that the layout matches with Launcher + */ + public void setHolesAllowedInLayout(boolean areHolesAllowed) { + if (mAreHolesAllowed != areHolesAllowed) { + mAreHolesAllowed = areHolesAllowed; + updateHotseatItemsVisibility(); + // TODO: Add animation } } - protected void cleanup() { - endAllLayoutTransitionAnimators(); - setLayoutTransition(null); - removeAllViews(); - } - - private void endAllLayoutTransitionAnimators() { - mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.CHANGING).end(); - mLayoutTransition.getAnimator(LayoutTransition.APPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING).end(); + private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) { + if (mAreHolesAllowed != areHolesAllowed) { + mAreHolesAllowed = areHolesAllowed; + updateHotseatItemsVisibility(); + onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), + makeMeasureSpec(getMeasuredHeight(), EXACTLY)); + onLayout(false, getLeft(), getTop(), getRight(), getBottom()); + } } /** @@ -192,10 +160,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa */ protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) { for (int i = 0; i < hotseatItemInfos.length; i++) { - ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i - : hotseatItemInfos.length - i - 1]; - int hotseatIndex = mHotseatStartIndex + i; - View hotseatView = getChildAt(hotseatIndex); + ItemInfo hotseatItemInfo = hotseatItemInfos[ + !mIsRtl ? i : hotseatItemInfos.length - i - 1]; + View hotseatView = mHotseatIconsContainer.getChildAt(i); // Replace any Hotseat views with the appropriate type if it's not already that type. final int expectedLayoutResId; @@ -213,23 +180,23 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } else { expectedLayoutResId = R.layout.taskbar_app_icon; } - if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId + if (hotseatView == null + || hotseatView.getSourceLayoutResId() != expectedLayoutResId || needsReinflate) { - removeView(hotseatView); - ActivityContext activityContext = getActivityContext(); + mHotseatIconsContainer.removeView(hotseatView); if (isFolder) { FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, - getActivityContext(), this, folderInfo); + mActivityContext, this, folderInfo); folderIcon.setTextVisible(false); hotseatView = folderIcon; } else { hotseatView = inflate(expectedLayoutResId); } - int iconSize = activityContext.getDeviceProfile().iconSizePx; + int iconSize = mActivityContext.getDeviceProfile().iconSizePx; LayoutParams lp = new LayoutParams(iconSize, iconSize); lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); - addView(hotseatView, hotseatIndex, lp); + mHotseatIconsContainer.addView(hotseatView, i, lp); } // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index. @@ -237,13 +204,11 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa && hotseatItemInfo instanceof WorkspaceItemInfo) { ((BubbleTextView) hotseatView).applyFromWorkspaceItem( (WorkspaceItemInfo) hotseatItemInfo); - hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); - hotseatView.setOnLongClickListener( - mControllerCallbacks.getItemOnLongClickListener()); + hotseatView.setOnClickListener(mIconClickListener); + hotseatView.setOnLongClickListener(mIconLongClickListener); } else if (isFolder) { - hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); - hotseatView.setOnLongClickListener( - mControllerCallbacks.getItemOnLongClickListener()); + hotseatView.setOnClickListener(mIconClickListener); + hotseatView.setOnLongClickListener(mIconLongClickListener); } else { hotseatView.setOnClickListener(null); hotseatView.setOnLongClickListener(null); @@ -254,24 +219,14 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } protected void updateHotseatItemsVisibility() { - for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) { - updateHotseatItemVisibility(getChildAt(i)); + for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) { + updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i)); } } private void updateHotseatItemVisibility(View hotseatView) { - if (hotseatView.getTag() != null) { - hotseatView.setVisibility(VISIBLE); - } else { - int oldVisibility = hotseatView.getVisibility(); - int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this); - hotseatView.setVisibility(newVisibility); - if (oldVisibility == GONE && newVisibility != GONE) { - // By default, the layout transition only runs when going to VISIBLE, - // but we want it to run when going to GONE to INVISIBLE as well. - getLayoutTransition().showChild(this, hotseatView, oldVisibility); - } - } + hotseatView.setVisibility( + hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE)); } @Override @@ -378,49 +333,20 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null; } - private void removeNavButtons() { - if (mButtonRegion != null) { - mButtonRegion.removeAllViews(); - removeView(mButtonRegion); - } // else We've never been in 3 button. Woah Scoob! - } - /** * Add back/home/recents buttons into a single ViewGroup that will be inserted at * {@param navButtonStartIndex} */ private void createNavButtons() { - ActivityContext context = getActivityContext(); - if (mButtonRegion == null) { - mButtonRegion = new LinearLayout(getContext()); - } else { - mButtonRegion.removeAllViews(); - } - mButtonRegion.setVisibility(VISIBLE); - LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( - context.getDeviceProfile().iconSizePx, - context.getDeviceProfile().iconSizePx + mActivityContext.getDeviceProfile().iconSizePx, + mActivityContext.getDeviceProfile().iconSizePx ); buttonParams.gravity = Gravity.CENTER; - View backButton = mButtonProvider.getBack(); - backButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_BACK)); - mButtonRegion.addView(backButton, buttonParams); - - // Home button - View homeButton = mButtonProvider.getHome(); - homeButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_HOME)); - mButtonRegion.addView(homeButton, buttonParams); - - View recentsButton = mButtonProvider.getRecents(); - recentsButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_RECENTS)); - mButtonRegion.addView(recentsButton, buttonParams); - - addView(mButtonRegion, mNavButtonStartIndex); + mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams); + mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams); + mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams); } @Override @@ -428,7 +354,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mIsDraggingItem = true; - AbstractFloatingView.closeAllOpenViews(getActivityContext()); + AbstractFloatingView.closeAllOpenViews(mActivityContext); return true; case DragEvent.ACTION_DRAG_ENDED: mIsDraggingItem = false; @@ -445,26 +371,26 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa * @return The bounding box of where the hotseat elements are relative to this TaskbarView. */ protected RectF getHotseatBounds() { - View firstHotseatView = null, lastHotseatView = null; - for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - if (firstHotseatView == null) { - firstHotseatView = child; - } - lastHotseatView = child; - } + RectF result; + mDisableRelayout = true; + boolean wereHolesAllowed = mAreHolesAllowed; + setHolesAllowedInLayoutNoAnimation(true); + result = new RectF( + mHotseatIconsContainer.getLeft(), + mHotseatIconsContainer.getTop(), + mHotseatIconsContainer.getRight(), + mHotseatIconsContainer.getBottom()); + setHolesAllowedInLayoutNoAnimation(wereHolesAllowed); + mDisableRelayout = false; + + return result; + } + + @Override + public void requestLayout() { + if (!mDisableRelayout) { + super.requestLayout(); } - if (firstHotseatView == null || lastHotseatView == null) { - return new RectF(); - } - View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView; - View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView; - return new RectF( - leftmostHotseatView.getLeft() - mItemMarginLeftRight, - leftmostHotseatView.getTop(), - rightmostHotseatView.getRight() + mItemMarginLeftRight, - rightmostHotseatView.getBottom()); } // FolderIconParent implemented methods. @@ -495,7 +421,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } private View inflate(@LayoutRes int layoutResId) { - return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false); + return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false); } @Override @@ -503,7 +429,11 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa // Ignore, we just implement Insettable to draw behind system insets. } - private T getActivityContext() { - return ActivityContext.lookupContext(getContext()); + public void setIconsVisibility(boolean isVisible) { + mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE); + } + + public boolean areIconsVisible() { + return mHotseatIconsContainer.getVisibility() == VISIBLE; } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index f5ddd0e52f..86bf1194bb 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -52,7 +52,6 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.taskbar.TaskbarController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.views.ScrimView; @@ -123,11 +122,6 @@ public abstract class BaseActivityInterface { - if (mTaskbarOverviewProxyDelegate == null) { - return; - } - mTaskbarOverviewProxyDelegate - .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher); - }); + int backDisposition, boolean showImeSwitcher) { + MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus( + displayId, vis, backDisposition, showImeSwitcher)); } - }; - public interface TaskbarOverviewProxyDelegate { - void updateImeStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher); + public TaskbarManager getTaskbarManager() { + return mTaskbarManager; + } } private static boolean sConnected = false; - private static TouchInteractionService sInstance; private static boolean sIsInitialized = false; private RotationTouchHelper mRotationTouchHelper; - @Nullable - private TaskbarOverviewProxyDelegate mTaskbarOverviewProxyDelegate; public static boolean isConnected() { return sConnected; } - @Nullable - public static TouchInteractionService getInstance() { - return sInstance; - } public static boolean isInitialized() { return sIsInitialized; @@ -336,9 +308,7 @@ public class TouchInteractionService extends Service implements PluginListener - - \ No newline at end of file diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 3d044d68ce..322c6eed29 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -447,10 +447,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * @param canvas The canvas to draw to. */ protected void drawDotIfNecessary(Canvas canvas) { - if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) { - // TODO: support notification dots in Taskbar - return; - } if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) { getIconBounds(mDotParams.iconBounds); Utilities.scaleRectAboutCenter(mDotParams.iconBounds, diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index b2a9e75e50..ff380ce1ae 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -49,7 +49,6 @@ public class Hotseat extends CellLayout implements Insettable { private final View mQsb; private final int mQsbHeight; - private final View mTaskbarView; private final int mTaskbarViewHeight; public Hotseat(Context context) { @@ -67,10 +66,7 @@ public class Hotseat extends CellLayout implements Insettable { mQsbHeight = mQsb.getLayoutParams().height; addView(mQsb); - mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false); - mTaskbarViewHeight = mTaskbarView.getLayoutParams().height; - // We want taskbar in the back so its background applies to Hotseat as well. - addView(mTaskbarView, 0); + mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size); } /** @@ -187,8 +183,6 @@ public class Hotseat extends CellLayout implements Insettable { int width = getShortcutsAndWidgets().getMeasuredWidth(); mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY)); - mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY)); } @Override @@ -202,13 +196,6 @@ public class Hotseat extends CellLayout implements Insettable { int bottom = b - t - getQsbOffsetY(); int top = bottom - mQsbHeight; mQsb.layout(left, top, right, bottom); - - int taskbarWidth = mTaskbarView.getMeasuredWidth(); - left = (r - l - taskbarWidth) / 2; - right = left + taskbarWidth; - bottom = b - t - getTaskbarOffsetY(); - top = bottom - mTaskbarViewHeight; - mTaskbarView.layout(left, top, right, bottom); } /** @@ -244,10 +231,4 @@ public class Hotseat extends CellLayout implements Insettable { return mQsb; } - /** - * Returns the Taskbar inside hotseat - */ - public View getTaskbarView() { - return mTaskbarView; - } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index cf902160ee..8889e60688 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1942,13 +1942,6 @@ public class Launcher extends StatefulActivity implements Launche @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { - if (isViewInTaskbar(v)) { - // Start the activity without the hacky workarounds below, which assume the View was - // clicked when Launcher was resumed and will be hidden until Launcher is re-resumed - // (this isn't the case for Taskbar). - return super.startActivitySafely(v, intent, item); - } - if (!hasBeenResumed()) { // Workaround an issue where the WM launch animation is clobbered when finishing the // recents animation into launcher. Defer launching the activity until Launcher is @@ -2860,13 +2853,6 @@ public class Launcher extends StatefulActivity implements Launche .start(); } - /** - * @return Whether the View is in the same window as the Taskbar window. - */ - public boolean isViewInTaskbar(View v) { - return false; - } - public boolean supportsAdaptiveIconAnimation(View clickedView) { return false; } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 7ae729aa0c..2884fba08d 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -324,15 +324,17 @@ public final class Utilities { } public static void scaleRectFAboutCenter(RectF r, float scale) { + scaleRectFAboutPivot(r, scale, r.centerX(), r.centerY()); + } + + public static void scaleRectFAboutPivot(RectF r, float scale, float px, float py) { if (scale != 1.0f) { - float cx = r.centerX(); - float cy = r.centerY(); - r.offset(-cx, -cy); + r.offset(-px, -py); r.left = r.left * scale; r.top = r.top * scale ; r.right = r.right * scale; r.bottom = r.bottom * scale; - r.offset(cx, cy); + r.offset(px, py); } } diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index ce7dc07d6d..b5dcd3a041 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -27,6 +27,7 @@ import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SU import android.app.AlertDialog; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.LauncherApps; @@ -193,6 +194,35 @@ public class ItemClickHandler { launcher.startActivitySafely(v, intent, item); } + /** + * Handles clicking on a disabled shortcut + */ + public static void handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) { + final int disabledFlags = shortcut.runtimeStatusFlags + & WorkspaceItemInfo.FLAG_DISABLED_MASK; + if ((disabledFlags + & ~FLAG_DISABLED_SUSPENDED + & ~FLAG_DISABLED_QUIET_USER) == 0) { + // If the app is only disabled because of the above flags, launch activity anyway. + // Framework will tell the user why the app is suspended. + } else { + if (!TextUtils.isEmpty(shortcut.disabledMessage)) { + // Use a message specific to this shortcut, if it has one. + Toast.makeText(context, shortcut.disabledMessage, Toast.LENGTH_SHORT).show(); + return; + } + // Otherwise just use a generic error message. + int error = R.string.activity_not_available; + if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) { + error = R.string.safemode_shortcut_error; + } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 + || (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) { + error = R.string.shortcut_not_available; + } + Toast.makeText(context, error, Toast.LENGTH_SHORT).show(); + } + } + /** * Event handler for an app shortcut click. * @@ -200,30 +230,8 @@ public class ItemClickHandler { */ public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) { if (shortcut.isDisabled()) { - final int disabledFlags = shortcut.runtimeStatusFlags - & WorkspaceItemInfo.FLAG_DISABLED_MASK; - if ((disabledFlags & - ~FLAG_DISABLED_SUSPENDED & - ~FLAG_DISABLED_QUIET_USER) == 0) { - // If the app is only disabled because of the above flags, launch activity anyway. - // Framework will tell the user why the app is suspended. - } else { - if (!TextUtils.isEmpty(shortcut.disabledMessage)) { - // Use a message specific to this shortcut, if it has one. - Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show(); - return; - } - // Otherwise just use a generic error message. - int error = R.string.activity_not_available; - if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) { - error = R.string.safemode_shortcut_error; - } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 || - (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) { - error = R.string.shortcut_not_available; - } - Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show(); - return; - } + handleDisabledItemClicked(shortcut, launcher); + return; } // Check for abandoned promise From f8b1c3b38ee438498097ffd06339050137589f2b Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 20 May 2021 20:18:47 +0000 Subject: [PATCH 20/22] Revert "Revert "Renaming TaskbarController to LauncherTaskbarUIC..." Revert submission 14664190 Reason for revert: Errorprone fixed in ag/14667108 Reverted Changes: Icd1ddd43e:Revert "Renaming TaskbarController to LauncherTask... I465013697:Revert "Moving taskbar lifecycle to TouchInteracti... Change-Id: I1dcccb43d8206830e2b7c1974e775c94ac8c542f Bug: 187353581 --- .../launcher3/BaseQuickstepLauncher.java | 20 ++++++++-------- .../HotseatPredictionController.java | 4 ++-- ....java => LauncherTaskbarUIController.java} | 9 +++---- .../taskbar/TaskbarAnimationController.java | 2 +- .../launcher3/taskbar/TaskbarManager.java | 4 ++-- .../uioverrides/QuickstepLauncher.java | 6 ++--- .../quickstep/LauncherActivityInterface.java | 24 +++++++++---------- 7 files changed, 35 insertions(+), 34 deletions(-) rename quickstep/src/com/android/launcher3/taskbar/{TaskbarController.java => LauncherTaskbarUIController.java} (97%) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 300654e657..0b41f15b42 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -50,7 +50,7 @@ import com.android.launcher3.proxy.StartActivityParams; import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.TaskbarController; +import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.taskbar.TaskbarStateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; @@ -97,7 +97,7 @@ public abstract class BaseQuickstepLauncher extends Launcher private OverviewActionsView mActionsView; private @Nullable TaskbarManager mTaskbarManager; - private @Nullable TaskbarController mTaskbarController; + private @Nullable LauncherTaskbarUIController mTaskbarUIController; private final ServiceConnection mTisBinderConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { @@ -262,8 +262,8 @@ public abstract class BaseQuickstepLauncher extends Launcher } - public void setTaskbarController(TaskbarController taskbarController) { - mTaskbarController = taskbarController; + public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { + mTaskbarUIController = taskbarUIController; } public T getActionsView() { @@ -293,8 +293,8 @@ public abstract class BaseQuickstepLauncher extends Launcher return mDepthController; } - public @Nullable TaskbarController getTaskbarController() { - return mTaskbarController; + public @Nullable LauncherTaskbarUIController getTaskbarUIController() { + return mTaskbarUIController; } public TaskbarStateHandler getTaskbarStateHandler() { @@ -350,8 +350,8 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override public float getNormalTaskbarScale() { - if (mTaskbarController != null) { - return mTaskbarController.getTaskbarScaleOnHome(); + if (mTaskbarUIController != null) { + return mTaskbarUIController.getTaskbarScaleOnHome(); } return super.getNormalTaskbarScale(); } @@ -373,8 +373,8 @@ public abstract class BaseQuickstepLauncher extends Launcher } if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { - if (mTaskbarController != null) { - mTaskbarController.onLauncherResumedOrPaused(hasBeenResumed()); + if (mTaskbarUIController != null) { + mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed()); } } diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 5dcf84c249..85e5ab0a9b 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -263,8 +263,8 @@ public class HotseatPredictionController implements DragController.DragListener, removeOutlineDrawings(); } - if (mLauncher.getTaskbarController() != null) { - mLauncher.getTaskbarController().onHotseatUpdated(); + if (mLauncher.getTaskbarUIController() != null) { + mLauncher.getTaskbarUIController().onHotseatUpdated(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java similarity index 97% rename from quickstep/src/com/android/launcher3/taskbar/TaskbarController.java rename to quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index cdae5beb9a..67264194b2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -38,7 +38,7 @@ import com.android.launcher3.states.StateAnimationConfig; * TODO: Rename to have Launcher prefix */ -public class TaskbarController extends TaskbarUIController { +public class LauncherTaskbarUIController extends TaskbarUIController { private final BaseQuickstepLauncher mLauncher; private final TaskbarStateHandler mTaskbarStateHandler; @@ -52,7 +52,8 @@ public class TaskbarController extends TaskbarUIController { private @Nullable Animator mAnimator; private boolean mIsAnimatingToLauncher; - public TaskbarController(BaseQuickstepLauncher launcher, TaskbarActivityContext context) { + public LauncherTaskbarUIController( + BaseQuickstepLauncher launcher, TaskbarActivityContext context) { mContext = context; mTaskbarContainerView = context.getDragLayer(); mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); @@ -72,7 +73,7 @@ public class TaskbarController extends TaskbarUIController { mHotseatController.init(); setTaskbarViewVisible(!mLauncher.hasBeenResumed()); alignRealHotseatWithTaskbar(); - mLauncher.setTaskbarController(this); + mLauncher.setTaskbarUIController(this); } @Override @@ -86,7 +87,7 @@ public class TaskbarController extends TaskbarUIController { mHotseatController.cleanup(); setTaskbarViewVisible(true); mLauncher.getHotseat().setIconsAlpha(1f); - mLauncher.setTaskbarController(null); + mLauncher.setTaskbarUIController(null); } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java index 815efb9728..e20ddf88ce 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java @@ -21,7 +21,7 @@ import android.animation.Animator; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Utilities; -import com.android.launcher3.taskbar.TaskbarController.TaskbarAnimationControllerCallbacks; +import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index b9eec93687..65c87bd279 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -97,7 +97,7 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen if (mTaskbarActivityContext != null) { mTaskbarActivityContext.setUIController(mLauncher == null ? TaskbarUIController.DEFAULT - : new TaskbarController(launcher, mTaskbarActivityContext)); + : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext)); } } @@ -115,7 +115,7 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen mTaskbarActivityContext.init(); if (mLauncher != null) { mTaskbarActivityContext.setUIController( - new TaskbarController(mLauncher, mTaskbarActivityContext)); + new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext)); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 45bb521336..f0b02b3c47 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -165,7 +165,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { // Only pause is taskbar controller is not present - mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null); + mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null); return super.startActivitySafely(v, intent, item); } @@ -233,9 +233,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void bindWorkspaceItemsChanged(List updated) { super.bindWorkspaceItemsChanged(updated); - if (getTaskbarController() != null && updated.stream() + if (getTaskbarUIController() != null && updated.stream() .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) { - getTaskbarController().onHotseatUpdated(); + getTaskbarUIController().onHotseatUpdated(); } } diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index ff69180d33..9014774ae7 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -41,7 +41,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; -import com.android.launcher3.taskbar.TaskbarController; +import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.SysUINavigationMode.Mode; @@ -163,12 +163,12 @@ public final class LauncherActivityInterface extends } @Nullable - private TaskbarController getTaskbarController() { + private LauncherTaskbarUIController getTaskbarController() { BaseQuickstepLauncher launcher = getCreatedActivity(); if (launcher == null) { return null; } - return launcher.getTaskbarController(); + return launcher.getTaskbarUIController(); } @Nullable @@ -276,13 +276,13 @@ public final class LauncherActivityInterface extends @Override public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget, long duration) { - TaskbarController taskbarController = getTaskbarController(); + LauncherTaskbarUIController uiController = getTaskbarController(); Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration); - if (taskbarController == null) { + if (uiController == null) { return superAnimator; } LauncherState toState = stateFromGestureEndTarget(endTarget); - Animator taskbarAnimator = taskbarController.createAnimToLauncher(toState, duration); + Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration); if (superAnimator == null) { return taskbarAnimator; } else { @@ -300,20 +300,20 @@ public final class LauncherActivityInterface extends @Override public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { - TaskbarController taskbarController = getTaskbarController(); - if (taskbarController == null) { + LauncherTaskbarUIController uiController = getTaskbarController(); + if (uiController == null) { return super.deferStartingActivity(deviceState, ev); } - return taskbarController.isEventOverAnyTaskbarItem(ev); + return uiController.isEventOverAnyTaskbarItem(ev); } @Override public boolean shouldCancelCurrentGesture() { - TaskbarController taskbarController = getTaskbarController(); - if (taskbarController == null) { + LauncherTaskbarUIController uiController = getTaskbarController(); + if (uiController == null) { return super.shouldCancelCurrentGesture(); } - return taskbarController.isDraggingItem(); + return uiController.isDraggingItem(); } @Override From 301eaf2b8c7bb49093f8a958de6d4f6901b676f1 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 20 May 2021 13:45:58 -0700 Subject: [PATCH 21/22] Initiate taskbar only after the user is unlocked Bug: 188755902 Test: Presubmit Change-Id: Ida4bc07f45396e82ab404a617aa6833afeea78bc --- .../android/launcher3/taskbar/TaskbarManager.java | 13 +++++++++++++ .../android/quickstep/TouchInteractionService.java | 1 + 2 files changed, 14 insertions(+) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index 65c87bd279..263aa1142d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -57,6 +57,8 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen private static final int CHANGE_FLAGS = CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS; + private boolean mUserUnlocked = false; + public TaskbarManager(TouchInteractionService service) { mDisplayController = DisplayController.INSTANCE.get(service); mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service); @@ -89,6 +91,14 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen } } + /** + * Called when the user is unlocked + */ + public void onUserUnlocked() { + mUserUnlocked = true; + recreateTaskbar(); + } + /** * Sets or clears a launcher to act as taskbar callback */ @@ -106,6 +116,9 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen if (!FeatureFlags.ENABLE_TASKBAR.get()) { return; } + if (!mUserUnlocked) { + return; + } DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext); if (!dp.isTaskbarPresent) { return; diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index d95f7b7fed..7956fcc562 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -325,6 +325,7 @@ public class TouchInteractionService extends Service implements PluginListener Date: Fri, 21 May 2021 10:49:03 +0100 Subject: [PATCH 22/22] Align 'No widgets available' text box properly in Widget picker. Bug: 186209657 Test: Tested manually Change-Id: I85fa43cd745a8c9f9601d25ed3a35f512506234d --- ...rchAndRecommendationsScrollController.java | 30 ++++++++++++++- .../widget/picker/WidgetsFullSheet.java | 37 +++++++++++++++---- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java index 6781824d74..664377999d 100644 --- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java +++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java @@ -21,6 +21,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; import android.widget.RelativeLayout; +import android.widget.TextView; import androidx.annotation.Nullable; @@ -39,9 +40,11 @@ final class SearchAndRecommendationsScrollController implements private final View mSearchAndRecommendationViewParent; private final WidgetsRecyclerView mPrimaryRecyclerView; private final WidgetsRecyclerView mSearchRecyclerView; + private final TextView mNoWidgetsView; private final int mTabsHeight; private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0); private final Point mTempOffset = new Point(); + private int mBottomInset; // The following are only non null if mHasWorkProfile is true. @Nullable private final WidgetsRecyclerView mWorkRecyclerView; @@ -81,7 +84,8 @@ final class SearchAndRecommendationsScrollController implements @Nullable WidgetsRecyclerView workRecyclerView, WidgetsRecyclerView searchRecyclerView, @Nullable View personalWorkTabsView, - @Nullable PersonalWorkPagedView primaryWorkViewPager) { + @Nullable PersonalWorkPagedView primaryWorkViewPager, + TextView noWidgetsView) { mHasWorkProfile = hasWorkProfile; mViewHolder = viewHolder; mViewHolder.mContainer.setSearchAndRecommendationScrollController(this); @@ -92,6 +96,7 @@ final class SearchAndRecommendationsScrollController implements mPrimaryWorkTabsView = personalWorkTabsView; mPrimaryWorkViewPager = primaryWorkViewPager; mTabsHeight = tabsHeight; + mNoWidgetsView = noWidgetsView; setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false); } @@ -113,6 +118,15 @@ final class SearchAndRecommendationsScrollController implements reset(animateReset); } + /** + * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever + * necessary. + */ + public boolean updateBottomInset(int bottomInset) { + mBottomInset = bottomInset; + return updateMarginAndPadding(); + } + /** * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views. * @@ -129,6 +143,8 @@ final class SearchAndRecommendationsScrollController implements + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable); int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer); + int noWidgetsViewHeight = topContainerHeight - mBottomInset; + if (mHasWorkProfile) { mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle) + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable); @@ -180,6 +196,10 @@ final class SearchAndRecommendationsScrollController implements int topOffsetAfterAllViewsCollapsed = topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs; + if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) { + noWidgetsViewHeight += mTabsHeight; + } + RelativeLayout.LayoutParams viewPagerLayoutParams = (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams(); if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) { @@ -222,6 +242,14 @@ final class SearchAndRecommendationsScrollController implements mSearchRecyclerView.getPaddingBottom()); hasMarginOrPaddingUpdated = true; } + if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) { + mNoWidgetsView.setPadding( + mNoWidgetsView.getPaddingLeft(), + noWidgetsViewHeight, + mNoWidgetsView.getPaddingRight(), + mNoWidgetsView.getPaddingBottom()); + hasMarginOrPaddingUpdated = true; + } return hasMarginOrPaddingUpdated; } diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 42ff249a4a..039cad8291 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -92,8 +92,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet private final boolean mHasWorkProfile; private final SparseArray mAdapters = new SparseArray(); private final UserHandle mCurrentUser = Process.myUserHandle(); - private final Predicate mPrimaryWidgetsFilter = entry -> - mCurrentUser.equals(entry.mPkgItem.user); + private final Predicate mPrimaryWidgetsFilter = + entry -> mCurrentUser.equals(entry.mPkgItem.user); private final Predicate mWorkWidgetsFilter = mPrimaryWidgetsFilter.negate(); private final OnLayoutChangeListener mLayoutChangeListenerToShowTips = @@ -131,6 +131,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView; @Nullable private PersonalWorkPagedView mViewPager; private boolean mIsInSearchMode; + private boolean mIsNoWidgetsViewNeeded; private int mMaxSpansPerRow = 4; private View mTabsView; private TextView mNoWidgetsView; @@ -187,6 +188,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout, true); + mNoWidgetsView = findViewById(R.id.no_widgets_text); mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder( findViewById(R.id.search_and_recommendations_container)); mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController( @@ -197,10 +199,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null, findViewById(R.id.search_widgets_list_view), mTabsView, - mViewPager); + mViewPager, + mNoWidgetsView); fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController); - mNoWidgetsView = findViewById(R.id.no_widgets_text); onRecommendedWidgetsBound(); onWidgetsBound(); @@ -295,6 +297,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mHasWorkProfile) { setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom); } + mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom); if (insets.bottom > 0) { setupNavBarColor(); } else { @@ -410,6 +413,16 @@ public class WidgetsFullSheet extends BaseWidgetSheet } else { updateRecyclerViewVisibility(primaryUserAdapterHolder); } + // Update recommended widgets section so that it occupies appropriate space on screen to + // leave enough space for presence/absence of mNoWidgetsView. + boolean isNoWidgetsViewNeeded = + mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0 + || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK) + .mWidgetsListAdapter.getItemCount() == 0); + if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) { + mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded; + onRecommendedWidgetsBound(); + } } @Override @@ -476,9 +489,19 @@ public class WidgetsFullSheet extends BaseWidgetSheet WidgetsRecommendationTableLayout table = mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable; if (recommendedWidgets.size() > 0) { - float maxTableHeight = - (mActivityContext.getDeviceProfile().availableHeightPx - mTabsHeight - - getHeaderViewHeight()) * RECOMMENDATION_TABLE_HEIGHT_RATIO; + float noWidgetsViewHeight = 0; + if (mIsNoWidgetsViewNeeded) { + // Make sure recommended section leaves enough space for noWidgetsView. + Rect noWidgetsViewTextBounds = new Rect(); + mNoWidgetsView.getPaint() + .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0, + mNoWidgetsView.getText().length(), noWidgetsViewTextBounds); + noWidgetsViewHeight = noWidgetsViewTextBounds.height(); + } + float maxTableHeight = (mActivityContext.getDeviceProfile().availableHeightPx + - mTabsHeight - getHeaderViewHeight() - noWidgetsViewHeight) + * RECOMMENDATION_TABLE_HEIGHT_RATIO; + List> recommendedWidgetsInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets, mMaxSpansPerRow);