Snap for 7473608 from 494544d8fa to tm-release
Change-Id: I1d5f0051c257fe569b16a738842effb088659a76
This commit is contained in:
@@ -90,12 +90,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
mHotseatController.init();
|
||||
mLauncher.setTaskbarUIController(this);
|
||||
mKeyguardController = taskbarControllers.taskbarKeyguardController;
|
||||
|
||||
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
|
||||
mIconAlignmentForResumedState.finishAnimation();
|
||||
onIconAlignmentRatioChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mIconAlignmentForResumedState.finishAnimation();
|
||||
mIconAlignmentForGestureState.finishAnimation();
|
||||
|
||||
mHotseatController.cleanup();
|
||||
setTaskbarViewVisible(true);
|
||||
mLauncher.getHotseat().setIconsAlpha(1f);
|
||||
|
||||
@@ -105,7 +105,7 @@ public class NavbarButtonsViewController {
|
||||
return true;
|
||||
};
|
||||
|
||||
if (mContext.canShowNavButtons()) {
|
||||
if (mContext.isThreeButtonNav()) {
|
||||
initButtons(mStartContainer, mEndContainer, mControllers.navButtonController);
|
||||
|
||||
// Animate taskbar background when IME shows
|
||||
|
||||
@@ -161,8 +161,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
|
||||
public boolean canShowNavButtons() {
|
||||
return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
|
||||
public boolean isThreeButtonNav() {
|
||||
return mNavMode == Mode.THREE_BUTTONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,7 +215,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
}
|
||||
|
||||
public void updateSysuiStateFlags(int systemUiStateFlags, boolean forceUpdate) {
|
||||
if (!canShowNavButtons()) {
|
||||
if (!isThreeButtonNav()) {
|
||||
return;
|
||||
}
|
||||
mControllers.navbarButtonsViewController.updateStateForSysuiFlags(
|
||||
|
||||
@@ -61,7 +61,7 @@ public class TaskbarControllers {
|
||||
*/
|
||||
public void init() {
|
||||
navbarButtonsViewController.init(this);
|
||||
if (taskbarActivityContext.canShowNavButtons()) {
|
||||
if (taskbarActivityContext.isThreeButtonNav()) {
|
||||
rotationButtonController.init();
|
||||
}
|
||||
taskbarDragLayerController.init(this);
|
||||
|
||||
@@ -93,7 +93,7 @@ public class TaskbarDragLayerController {
|
||||
*/
|
||||
public void updateInsetsTouchability(InsetsInfo insetsInfo) {
|
||||
insetsInfo.touchableRegion.setEmpty();
|
||||
if (mActivity.canShowNavButtons()) {
|
||||
if (mActivity.isThreeButtonNav()) {
|
||||
// Always have nav buttons be touchable
|
||||
mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
|
||||
mTaskbarDragLayer, insetsInfo.touchableRegion);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TaskbarKeyguardController {
|
||||
mKeyguardSysuiFlags = interestingKeyguardFlags;
|
||||
|
||||
mBouncerShowing = bouncerShowing;
|
||||
if (!mContext.canShowNavButtons()) {
|
||||
if (!mContext.isThreeButtonNav()) {
|
||||
// For gesture nav we don't need to deal with bouncer or showing taskbar when locked
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,10 +94,7 @@ public class TaskbarNavButtonController {
|
||||
}
|
||||
|
||||
private void navigateToOverview() {
|
||||
int commandType = mService.getOverviewCommandHelper().isOverviewVisible() ?
|
||||
OverviewCommandHelper.TYPE_TOGGLE :
|
||||
OverviewCommandHelper.TYPE_SHOW;
|
||||
mService.getOverviewCommandHelper().addCommand(commandType);
|
||||
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
|
||||
}
|
||||
|
||||
private void executeBack() {
|
||||
|
||||
@@ -136,11 +136,6 @@ public class OverviewCommandHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOverviewVisible() {
|
||||
BaseActivityInterface activityInterface =
|
||||
mOverviewComponentObserver.getActivityInterface();
|
||||
return activityInterface.getVisibleRecentsView() != null;
|
||||
}
|
||||
/**
|
||||
* Executes the task and returns true if next task can be executed. If false, then the next
|
||||
* task is deferred until {@link #scheduleNextTask} is called
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
<!-- View IDs to store item highlight information -->
|
||||
<item type="id" name="view_unhighlight_background" />
|
||||
|
||||
<!-- view ID used to restore work tab state -->
|
||||
<item type="id" name="work_tab_state_id" />
|
||||
|
||||
<!-- Menu id for feature flags -->
|
||||
<item type="id" name="menu_apply_flags" />
|
||||
|
||||
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Size;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.testing.TestActivity;
|
||||
import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CachingWidgetPreviewLoaderTest {
|
||||
private static final Size SIZE_10_10 = new Size(10, 10);
|
||||
private static final Size SIZE_20_20 = new Size(20, 20);
|
||||
private static final String TEST_PACKAGE = "com.example.test";
|
||||
private static final ComponentName TEST_PROVIDER =
|
||||
new ComponentName(TEST_PACKAGE, ".WidgetProvider");
|
||||
private static final ComponentName TEST_PROVIDER2 =
|
||||
new ComponentName(TEST_PACKAGE, ".WidgetProvider2");
|
||||
private static final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
|
||||
private static final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);
|
||||
|
||||
|
||||
@Mock private CancellationSignal mCancellationSignal;
|
||||
@Mock private WidgetPreviewLoader mDelegate;
|
||||
@Mock private IconCache mIconCache;
|
||||
@Mock private DeviceProfile mDeviceProfile;
|
||||
@Mock private LauncherAppWidgetProviderInfo mProviderInfo;
|
||||
@Mock private LauncherAppWidgetProviderInfo mProviderInfo2;
|
||||
@Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback;
|
||||
@Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2;
|
||||
@Captor private ArgumentCaptor<WidgetPreviewLoadedCallback> mCallbackCaptor;
|
||||
|
||||
private TestActivity mTestActivity;
|
||||
private CachingWidgetPreviewLoader mLoader;
|
||||
private WidgetItem mWidgetItem;
|
||||
private WidgetItem mWidgetItem2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mLoader = new CachingWidgetPreviewLoader(mDelegate);
|
||||
|
||||
mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get();
|
||||
mTestActivity.setDeviceProfile(mDeviceProfile);
|
||||
|
||||
when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal);
|
||||
|
||||
mProviderInfo.provider = TEST_PROVIDER;
|
||||
when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0));
|
||||
|
||||
mProviderInfo2.provider = TEST_PROVIDER2;
|
||||
when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0));
|
||||
|
||||
InvariantDeviceProfile testProfile = new InvariantDeviceProfile();
|
||||
testProfile.numRows = 5;
|
||||
testProfile.numColumns = 5;
|
||||
|
||||
mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache);
|
||||
mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_notInCache_shouldReturnNull() {
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_notInCache_shouldNotCallDelegate() {
|
||||
mLoader.getPreview(mWidgetItem, SIZE_10_10);
|
||||
|
||||
verifyZeroInteractions(mDelegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_inCache_shouldReturnCachedBitmap() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_otherSizeInCache_shouldReturnNull() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_otherItemInCache_shouldReturnNull() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreview_shouldStoreMultipleSizesPerItem() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2);
|
||||
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_notInCache_shouldStartLoading() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
|
||||
verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any());
|
||||
verifyZeroInteractions(mPreviewLoadedCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenLoaded_shouldCallBack() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
|
||||
WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
|
||||
|
||||
loaderCallback.onPreviewLoaded(BITMAP);
|
||||
|
||||
verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenCancelled_shouldCancelDelegateRequest() {
|
||||
CancellationSignal cancellationSignal =
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
|
||||
cancellationSignal.cancel();
|
||||
|
||||
verify(mCancellationSignal).cancel();
|
||||
verifyZeroInteractions(mPreviewLoadedCallback);
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() {
|
||||
CancellationSignal cancellationSignal1 =
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
|
||||
|
||||
cancellationSignal1.cancel();
|
||||
|
||||
verifyZeroInteractions(mCancellationSignal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() {
|
||||
CancellationSignal cancellationSignal1 =
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
|
||||
WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
|
||||
|
||||
cancellationSignal1.cancel();
|
||||
loaderCallback.onPreviewLoaded(BITMAP);
|
||||
|
||||
verifyZeroInteractions(mPreviewLoadedCallback);
|
||||
verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() {
|
||||
CancellationSignal cancellationSignal1 =
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
CancellationSignal cancellationSignal2 =
|
||||
mLoader.loadPreview(
|
||||
mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
|
||||
|
||||
cancellationSignal1.cancel();
|
||||
cancellationSignal2.cancel();
|
||||
|
||||
verify(mCancellationSignal).cancel();
|
||||
verifyZeroInteractions(mPreviewLoadedCallback);
|
||||
verifyZeroInteractions(mPreviewLoadedCallback2);
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
|
||||
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
|
||||
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
|
||||
WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
|
||||
|
||||
loaderCallback.onPreviewLoaded(BITMAP);
|
||||
|
||||
verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
|
||||
verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_inCache_shouldCallBackImmediately() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
reset(mDelegate);
|
||||
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
|
||||
verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
|
||||
verifyZeroInteractions(mDelegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() {
|
||||
CancellationSignal cancellationSignal =
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
|
||||
WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
|
||||
loaderCallback.onPreviewLoaded(BITMAP);
|
||||
|
||||
cancellationSignal.cancel();
|
||||
|
||||
assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPreviewLoaded_notLoaded_shouldReturnFalse() {
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() {
|
||||
loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPreviewLoaded_loaded_shouldReturnTrue() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_notInCache_shouldBeNoOp() {
|
||||
mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_inCache_shouldRemovePreview() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
|
||||
|
||||
mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
|
||||
|
||||
mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
|
||||
|
||||
mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2));
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearPreviews_loading_shouldCancelLoad() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
|
||||
mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
|
||||
|
||||
verify(mCancellationSignal).cancel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_cacheEmpty_shouldBeNoOp() {
|
||||
mLoader.clearAll();
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_inCache_shouldRemovePreview() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
|
||||
mLoader.clearAll();
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
|
||||
|
||||
mLoader.clearAll();
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_inCache_multipleItems_shouldRemoveAll() {
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
|
||||
loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP);
|
||||
|
||||
mLoader.clearAll();
|
||||
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
|
||||
assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_loading_shouldCancelLoad() {
|
||||
mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
|
||||
|
||||
mLoader.clearAll();
|
||||
|
||||
verify(mCancellationSignal).cancel();
|
||||
}
|
||||
|
||||
private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) {
|
||||
reset(mDelegate);
|
||||
mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {});
|
||||
verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
|
||||
WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
|
||||
|
||||
loaderCallback.onPreviewLoaded(bitmap);
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -207,10 +207,9 @@ public final class WidgetsDiffReporterTest {
|
||||
// GIVEN the current list has app headers [A, B, E content].
|
||||
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
|
||||
List.of(mHeaderA, mHeaderB, mContentE));
|
||||
// GIVEN the new list has app headers [A, B, E content].
|
||||
List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderB, mContentE);
|
||||
// GIVEN the user has interacted with B.
|
||||
mHeaderB.setIsWidgetListShown(true);
|
||||
// GIVEN the new list has app headers [A, B, E content] and the user has interacted with B.
|
||||
List<WidgetsListBaseEntry> newList =
|
||||
List.of(mHeaderA, mHeaderB.withWidgetListShown(), mContentE);
|
||||
|
||||
// WHEN computing the list difference.
|
||||
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
|
||||
|
||||
+2
-2
@@ -33,13 +33,13 @@ import android.view.LayoutInflater;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.ComponentWithLabel;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
@@ -64,7 +64,7 @@ public final class WidgetsListAdapterTest {
|
||||
private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
|
||||
|
||||
@Mock private LayoutInflater mMockLayoutInflater;
|
||||
@Mock private WidgetPreviewLoader mMockWidgetCache;
|
||||
@Mock private DatabaseWidgetPreviewLoader mMockWidgetCache;
|
||||
@Mock private RecyclerView.AdapterDataObserver mListener;
|
||||
@Mock private IconCache mIconCache;
|
||||
|
||||
|
||||
+2
-2
@@ -34,7 +34,6 @@ import android.widget.TextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.ComponentWithLabel;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
@@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.testing.TestActivity;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class WidgetsListHeaderViewHolderBinderTest {
|
||||
@Mock
|
||||
private DeviceProfile mDeviceProfile;
|
||||
@Mock
|
||||
private WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
@Mock
|
||||
private OnHeaderClickListener mOnHeaderClickListener;
|
||||
|
||||
|
||||
+2
-2
@@ -34,7 +34,6 @@ import android.widget.TextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.ComponentWithLabel;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
@@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.testing.TestActivity;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest {
|
||||
@Mock
|
||||
private DeviceProfile mDeviceProfile;
|
||||
@Mock
|
||||
private WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
@Mock
|
||||
private OnHeaderClickListener mOnHeaderClickListener;
|
||||
|
||||
|
||||
+4
-4
@@ -38,13 +38,14 @@ import android.widget.TextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.ComponentWithLabel;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.testing.TestActivity;
|
||||
import com.android.launcher3.widget.CachingWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
@@ -85,7 +86,7 @@ public final class WidgetsListTableViewHolderBinderTest {
|
||||
@Mock
|
||||
private IconCache mIconCache;
|
||||
@Mock
|
||||
private WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
@Mock
|
||||
private DeviceProfile mDeviceProfile;
|
||||
|
||||
@@ -113,11 +114,10 @@ public final class WidgetsListTableViewHolderBinderTest {
|
||||
/* iconClickListener= */ view -> {},
|
||||
/* iconLongClickListener= */ view -> false);
|
||||
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
|
||||
mContext,
|
||||
LayoutInflater.from(mTestActivity),
|
||||
mOnIconClickListener,
|
||||
mOnLongClickListener,
|
||||
mWidgetPreviewLoader,
|
||||
new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
|
||||
new WidgetsListDrawableFactory(mTestActivity),
|
||||
widgetsListAdapter);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
|
||||
public class LauncherAppState {
|
||||
@@ -63,7 +64,7 @@ public class LauncherAppState {
|
||||
private final LauncherModel mModel;
|
||||
private final IconProvider mIconProvider;
|
||||
private final IconCache mIconCache;
|
||||
private final WidgetPreviewLoader mWidgetCache;
|
||||
private final DatabaseWidgetPreviewLoader mWidgetCache;
|
||||
private final InvariantDeviceProfile mInvariantDeviceProfile;
|
||||
private final RunnableList mOnTerminateCallback = new RunnableList();
|
||||
|
||||
@@ -138,7 +139,7 @@ public class LauncherAppState {
|
||||
mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context));
|
||||
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
|
||||
iconCacheFileName, mIconProvider);
|
||||
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
|
||||
mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache);
|
||||
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
|
||||
mOnTerminateCallback.add(mIconCache::close);
|
||||
}
|
||||
@@ -180,7 +181,7 @@ public class LauncherAppState {
|
||||
return mModel;
|
||||
}
|
||||
|
||||
public WidgetPreviewLoader getWidgetCache() {
|
||||
public DatabaseWidgetPreviewLoader getWidgetCache() {
|
||||
return mWidgetCache;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.text.Selection;
|
||||
@@ -82,6 +83,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener,
|
||||
ScrimView.ScrimDrawingController {
|
||||
|
||||
private static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
|
||||
|
||||
public static final float PULL_MULTIPLIER = .02f;
|
||||
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
|
||||
|
||||
@@ -181,6 +184,23 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
} catch (Exception e) {
|
||||
Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e);
|
||||
}
|
||||
Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null);
|
||||
if (state != null) {
|
||||
int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
|
||||
if (currentPage != 0) {
|
||||
rebindAdapters(true);
|
||||
mViewPager.setCurrentPage(currentPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
|
||||
super.dispatchSaveInstanceState(container);
|
||||
Bundle state = new Bundle();
|
||||
state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage());
|
||||
container.put(R.id.work_tab_state_id, state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +251,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
|
||||
|
||||
private void resetWorkProfile() {
|
||||
boolean isEnabled = !mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED);
|
||||
mWorkModeSwitch.updateCurrentState(isEnabled);
|
||||
if (mWorkModeSwitch != null) {
|
||||
mWorkModeSwitch.updateCurrentState(isEnabled);
|
||||
}
|
||||
mWorkAdapterProvider.updateCurrentState(isEnabled);
|
||||
mAH[AdapterHolder.WORK].applyPadding();
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
setSelected(true);
|
||||
setOnClickListener(this);
|
||||
if (Utilities.ATLEAST_R) {
|
||||
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
|
||||
|
||||
@@ -26,6 +26,7 @@ import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Fragment;
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
@@ -83,6 +84,8 @@ import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHost;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.NavigableAppWidgetHostView;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
@@ -202,6 +205,7 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
private final InsettableFrameLayout mRootView;
|
||||
private final Hotseat mHotseat;
|
||||
private final CellLayout mWorkspace;
|
||||
private final AppWidgetHost mAppWidgetHost;
|
||||
|
||||
public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
|
||||
super(context);
|
||||
@@ -255,6 +259,10 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
mDp.workspacePadding.top,
|
||||
mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
|
||||
mDp.workspacePadding.bottom);
|
||||
|
||||
mAppWidgetHost = FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()
|
||||
? new LauncherPreviewAppWidgetHost(context)
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Populate preview and render it. */
|
||||
@@ -354,14 +362,20 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
|
||||
private void inflateAndAddWidgets(
|
||||
LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
|
||||
AppWidgetHostView view = new NavigableAppWidgetHostView(this) {
|
||||
@Override
|
||||
protected boolean shouldAllowDirectClick() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
view.setAppWidget(-1, providerInfo);
|
||||
view.updateAppWidget(null);
|
||||
AppWidgetHostView view;
|
||||
if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) {
|
||||
view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo);
|
||||
} else {
|
||||
view = new NavigableAppWidgetHostView(this) {
|
||||
@Override
|
||||
protected boolean shouldAllowDirectClick() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
view.setAppWidget(-1, providerInfo);
|
||||
view.updateAppWidget(null);
|
||||
}
|
||||
|
||||
view.setTag(info);
|
||||
addInScreenFromBind(view, info);
|
||||
}
|
||||
@@ -477,4 +491,31 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
|
||||
view.layout(0, 0, width, height);
|
||||
}
|
||||
|
||||
private class LauncherPreviewAppWidgetHost extends AppWidgetHost {
|
||||
|
||||
private LauncherPreviewAppWidgetHost(Context context) {
|
||||
super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AppWidgetHostView onCreateView(
|
||||
Context context,
|
||||
int appWidgetId,
|
||||
AppWidgetProviderInfo appWidget) {
|
||||
return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LauncherPreviewAppWidgetHostView extends BaseLauncherAppWidgetHostView {
|
||||
|
||||
private LauncherPreviewAppWidgetHostView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAllowDirectClick() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
@@ -40,6 +41,7 @@ import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
@@ -50,6 +52,8 @@ import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.TimingLogger;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
@@ -756,6 +760,9 @@ public class LoaderTask implements Runnable {
|
||||
if (widgetProviderInfo != null
|
||||
&& (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
|
||||
|| appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
|
||||
logDeleteWidgetInfo(mApp.getInvariantDeviceProfile(),
|
||||
widgetProviderInfo);
|
||||
|
||||
// This can happen when display size changes.
|
||||
c.markDeleted("Widget removed, min sizes not met: "
|
||||
+ "span=" + appWidgetInfo.spanX + "x"
|
||||
@@ -982,6 +989,51 @@ public class LoaderTask implements Runnable {
|
||||
&& (provider.provider.getPackageName() != null);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi") // Already added API check.
|
||||
private static void logDeleteWidgetInfo(InvariantDeviceProfile idp,
|
||||
LauncherAppWidgetProviderInfo widgetProviderInfo) {
|
||||
FileLog.d(TAG, "Deleting " + widgetProviderInfo.getComponent()
|
||||
+ " due to min size constraint");
|
||||
Point cellSize = new Point();
|
||||
for (DeviceProfile deviceProfile : idp.supportedProfiles) {
|
||||
deviceProfile.getCellSize(cellSize);
|
||||
FileLog.d(TAG, "DeviceProfile available width: " + deviceProfile.availableWidthPx
|
||||
+ ", available height: " + deviceProfile.availableHeightPx
|
||||
+ ", cellLayoutBorderSpacingPx: " + deviceProfile.cellLayoutBorderSpacingPx
|
||||
+ ", cellSize: " + cellSize);
|
||||
}
|
||||
|
||||
StringBuilder widgetDimension = new StringBuilder();
|
||||
widgetDimension.append("Widget dimensions:\n")
|
||||
.append("minResizeWidth: ")
|
||||
.append(widgetProviderInfo.minResizeWidth)
|
||||
.append("\n")
|
||||
.append("minResizeHeight: ")
|
||||
.append(widgetProviderInfo.minResizeHeight)
|
||||
.append("\n")
|
||||
.append("defaultWidth: ")
|
||||
.append(widgetProviderInfo.minWidth)
|
||||
.append("\n")
|
||||
.append("defaultHeight: ")
|
||||
.append(widgetProviderInfo.minHeight)
|
||||
.append("\n");
|
||||
if (Utilities.ATLEAST_S) {
|
||||
widgetDimension.append("targetCellWidth: ")
|
||||
.append(widgetProviderInfo.targetCellWidth)
|
||||
.append("\n")
|
||||
.append("targetCellHeight: ")
|
||||
.append(widgetProviderInfo.targetCellHeight)
|
||||
.append("\n")
|
||||
.append("maxResizeWidth: ")
|
||||
.append(widgetProviderInfo.maxResizeWidth)
|
||||
.append("\n")
|
||||
.append("maxResizeHeight: ")
|
||||
.append(widgetProviderInfo.maxResizeHeight)
|
||||
.append("\n");
|
||||
}
|
||||
FileLog.d(TAG, widgetDimension.toString());
|
||||
}
|
||||
|
||||
private static void logASplit(final TimingLogger logger, final String label) {
|
||||
logger.addSplit(label);
|
||||
if (DEBUG) {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.Utilities.ATLEAST_S;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -59,4 +63,15 @@ public class WidgetItem extends ComponentKey {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
|
||||
@SuppressLint("NewApi") // Already added API check.
|
||||
public boolean hasPreviewLayout() {
|
||||
return ATLEAST_S && widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
|
||||
}
|
||||
|
||||
/** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
|
||||
public boolean isShortcut() {
|
||||
return activityInfo != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,9 +252,9 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
|
||||
view.setBackgroundResource(R.drawable.single_item_primary);
|
||||
} else if (totalVisibleShortcuts > 1) {
|
||||
if (numVisibleShortcut == 0) {
|
||||
view.setBackground(mRoundedTop);
|
||||
view.setBackground(mRoundedTop.getConstantState().newDrawable());
|
||||
} else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) {
|
||||
view.setBackground(mRoundedBottom);
|
||||
view.setBackground(mRoundedBottom.getConstantState().newDrawable());
|
||||
} else {
|
||||
view.setBackgroundResource(R.drawable.middle_item_primary);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,13 @@ package com.android.launcher3.shortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -30,16 +36,20 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.BubbleTextHolder;
|
||||
|
||||
/**
|
||||
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
|
||||
* This lets us animate the DeepShortcutView (icon and text) separately from the background.
|
||||
* A {@link android.widget.FrameLayout} that contains an icon and a {@link BubbleTextView} for text.
|
||||
* This lets us animate the child BubbleTextView's background (transparent ripple) separately from
|
||||
* the {@link DeepShortcutView} background color.
|
||||
*/
|
||||
public class DeepShortcutView extends FrameLayout implements BubbleTextHolder {
|
||||
|
||||
private static final Point sTempPoint = new Point();
|
||||
|
||||
private final Drawable mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT);
|
||||
|
||||
private BubbleTextView mBubbleText;
|
||||
private View mIconView;
|
||||
|
||||
@@ -63,6 +73,43 @@ public class DeepShortcutView extends FrameLayout implements BubbleTextHolder {
|
||||
super.onFinishInflate();
|
||||
mBubbleText = findViewById(R.id.bubble_text);
|
||||
mIconView = findViewById(R.id.icon);
|
||||
tryUpdateTextBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Drawable background) {
|
||||
super.setBackground(background);
|
||||
tryUpdateTextBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundResource(int resid) {
|
||||
super.setBackgroundResource(resid);
|
||||
tryUpdateTextBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text background to match the shape of this background (when applicable).
|
||||
*/
|
||||
private void tryUpdateTextBackground() {
|
||||
if (!(getBackground() instanceof GradientDrawable) || mBubbleText == null) {
|
||||
return;
|
||||
}
|
||||
GradientDrawable background = (GradientDrawable) getBackground();
|
||||
|
||||
int color = Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight);
|
||||
GradientDrawable backgroundMask = new GradientDrawable();
|
||||
backgroundMask.setColor(color);
|
||||
backgroundMask.setShape(GradientDrawable.RECTANGLE);
|
||||
if (background.getCornerRadii() != null) {
|
||||
backgroundMask.setCornerRadii(background.getCornerRadii());
|
||||
} else {
|
||||
backgroundMask.setCornerRadius(background.getCornerRadius());
|
||||
}
|
||||
|
||||
RippleDrawable drawable = new RippleDrawable(ColorStateList.valueOf(color),
|
||||
mTransparentDrawable, backgroundMask);
|
||||
mBubbleText.setBackground(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -108,8 +108,6 @@ public class SpringRelativeLayout extends RelativeLayout {
|
||||
switch (direction) {
|
||||
case DIRECTION_TOP:
|
||||
return new EdgeEffectProxy(getContext(), mEdgeGlowTop);
|
||||
case DIRECTION_BOTTOM:
|
||||
return new EdgeEffectProxy(getContext(), mEdgeGlowBottom);
|
||||
}
|
||||
return super.createEdgeEffect(view, direction);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.Executors;
|
||||
|
||||
/**
|
||||
* Launcher AppWidgetHostView with support for rounded corners and a fallback View.
|
||||
*/
|
||||
public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView {
|
||||
|
||||
protected final LayoutInflater mInflater;
|
||||
|
||||
private final Rect mEnforcedRectangle = new Rect();
|
||||
private final float mEnforcedCornerRadius;
|
||||
private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
|
||||
outline.setEmpty();
|
||||
} else {
|
||||
outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public BaseLauncherAppWidgetHostView(Context context) {
|
||||
super(context);
|
||||
|
||||
setExecutor(Executors.THREAD_POOL_EXECUTOR);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getErrorView() {
|
||||
return mInflater.inflate(R.layout.appwidget_error, this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fall back to error layout instead of showing widget.
|
||||
*/
|
||||
public void switchToErrorView() {
|
||||
// Update the widget with 0 Layout id, to reset the view to error view.
|
||||
updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
try {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
} catch (final RuntimeException e) {
|
||||
post(this::switchToErrorView);
|
||||
}
|
||||
|
||||
enforceRoundedCorners();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void resetRoundedCorners() {
|
||||
setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
setClipToOutline(false);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void enforceRoundedCorners() {
|
||||
if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
|
||||
resetRoundedCorners();
|
||||
return;
|
||||
}
|
||||
View background = RoundedCornerEnforcement.findBackground(this);
|
||||
if (background == null
|
||||
|| RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
|
||||
resetRoundedCorners();
|
||||
return;
|
||||
}
|
||||
RoundedCornerEnforcement.computeRoundedRectangle(this,
|
||||
background,
|
||||
mEnforcedRectangle);
|
||||
setOutlineProvider(mCornerRadiusEnforcementOutline);
|
||||
setClipToOutline(true);
|
||||
}
|
||||
|
||||
/** Returns the corner radius currently enforced, in pixels. */
|
||||
public float getEnforcedCornerRadius() {
|
||||
return mEnforcedCornerRadius;
|
||||
}
|
||||
|
||||
/** Returns true if the corner radius are enforced for this App Widget. */
|
||||
public boolean hasEnforcedCornerRadius() {
|
||||
return getClipToOutline();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.collection.ArrayMap;
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Wrapper around {@link DatabaseWidgetPreviewLoader} that contains caching logic. */
|
||||
public class CachingWidgetPreviewLoader implements WidgetPreviewLoader {
|
||||
|
||||
@NonNull private final WidgetPreviewLoader mDelegate;
|
||||
@NonNull private final Map<ComponentKey, Map<Size, CacheResult>> mCache = new ArrayMap<>();
|
||||
|
||||
public CachingWidgetPreviewLoader(@NonNull WidgetPreviewLoader delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
/** Returns whether the preview is loaded for the item and size. */
|
||||
public boolean isPreviewLoaded(@NonNull WidgetItem item, @NonNull Size previewSize) {
|
||||
return getPreview(item, previewSize) != null;
|
||||
}
|
||||
|
||||
/** Returns the cached preview for the item and size, or null if there is none. */
|
||||
@Nullable
|
||||
public Bitmap getPreview(@NonNull WidgetItem item, @NonNull Size previewSize) {
|
||||
CacheResult cacheResult = getCacheResult(item, previewSize);
|
||||
if (cacheResult instanceof CacheResult.Loaded) {
|
||||
return ((CacheResult.Loaded) cacheResult).mBitmap;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CacheResult getCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
|
||||
synchronized (mCache) {
|
||||
Map<Size, CacheResult> cacheResults = mCache.get(toComponentKey(item));
|
||||
if (cacheResults == null) {
|
||||
return CacheResult.MISS;
|
||||
}
|
||||
|
||||
return cacheResults.getOrDefault(previewSize, CacheResult.MISS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the result in the cache for the item and size. Returns the value previously in the
|
||||
* cache, or null if there was none.
|
||||
*/
|
||||
@Nullable
|
||||
private CacheResult putCacheResult(
|
||||
@NonNull WidgetItem item,
|
||||
@NonNull Size previewSize,
|
||||
@Nullable CacheResult cacheResult) {
|
||||
ComponentKey key = toComponentKey(item);
|
||||
synchronized (mCache) {
|
||||
Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
|
||||
CacheResult previous;
|
||||
if (cacheResult == null) {
|
||||
previous = cacheResults.remove(previewSize);
|
||||
if (cacheResults.isEmpty()) {
|
||||
mCache.remove(key);
|
||||
} else {
|
||||
previous = cacheResults.put(previewSize, cacheResult);
|
||||
mCache.put(key, cacheResults);
|
||||
}
|
||||
} else {
|
||||
previous = cacheResults.put(previewSize, cacheResult);
|
||||
mCache.put(key, cacheResults);
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
|
||||
ComponentKey key = toComponentKey(item);
|
||||
synchronized (mCache) {
|
||||
Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
|
||||
cacheResults.remove(previewSize);
|
||||
mCache.put(key, cacheResults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preview for the widget item and size, using the value in the cache if stored.
|
||||
*
|
||||
* @return a {@link CancellationSignal}, which can cancel the request before it loads
|
||||
*/
|
||||
@Override
|
||||
@UiThread
|
||||
@NonNull
|
||||
public CancellationSignal loadPreview(
|
||||
@NonNull BaseActivity activity, @NonNull WidgetItem item, @NonNull Size previewSize,
|
||||
@NonNull WidgetPreviewLoadedCallback callback) {
|
||||
CancellationSignal signal = new CancellationSignal();
|
||||
signal.setOnCancelListener(() -> {
|
||||
synchronized (mCache) {
|
||||
CacheResult cacheResult = getCacheResult(item, previewSize);
|
||||
if (!(cacheResult instanceof CacheResult.Loading)) {
|
||||
// If the key isn't actively loading, then this is a no-op. Cancelling loading
|
||||
// shouldn't clear the cache if we've already loaded.
|
||||
return;
|
||||
}
|
||||
|
||||
CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
|
||||
CacheResult.Loading updated = prev.withoutCallback(callback);
|
||||
|
||||
if (updated.mCallbacks.isEmpty()) {
|
||||
// If the last callback was removed, then cancel the underlying request in the
|
||||
// delegate.
|
||||
prev.mCancellationSignal.cancel();
|
||||
removeCacheResult(item, previewSize);
|
||||
} else {
|
||||
// If there are other callbacks still active, then don't cancel the delegate's
|
||||
// request, just remove this callback from the set.
|
||||
putCacheResult(item, previewSize, updated);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (mCache) {
|
||||
CacheResult cacheResult = getCacheResult(item, previewSize);
|
||||
if (cacheResult instanceof CacheResult.Loaded) {
|
||||
// If the bitmap is already present in the cache, invoke the callback immediately.
|
||||
callback.onPreviewLoaded(((CacheResult.Loaded) cacheResult).mBitmap);
|
||||
return signal;
|
||||
}
|
||||
|
||||
if (cacheResult instanceof CacheResult.Loading) {
|
||||
// If we're already loading the preview for this key, then just add the callback
|
||||
// to the set we'll call after it loads.
|
||||
CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
|
||||
putCacheResult(item, previewSize, prev.withCallback(callback));
|
||||
return signal;
|
||||
}
|
||||
|
||||
CancellationSignal delegateCancellationSignal =
|
||||
mDelegate.loadPreview(
|
||||
activity,
|
||||
item,
|
||||
previewSize,
|
||||
preview -> {
|
||||
CacheResult prev;
|
||||
synchronized (mCache) {
|
||||
prev = putCacheResult(
|
||||
item, previewSize, new CacheResult.Loaded(preview));
|
||||
}
|
||||
if (prev instanceof CacheResult.Loading) {
|
||||
// Notify each stored callback that the preview has loaded.
|
||||
((CacheResult.Loading) prev).mCallbacks
|
||||
.forEach(c -> c.onPreviewLoaded(preview));
|
||||
} else {
|
||||
// If there isn't a loading object in the cache, then we were
|
||||
// notified before adding this signal to the cache. Just
|
||||
// call back to the provided callback, there can't be others.
|
||||
callback.onPreviewLoaded(preview);
|
||||
}
|
||||
});
|
||||
ArraySet<WidgetPreviewLoadedCallback> callbacks = new ArraySet<>();
|
||||
callbacks.add(callback);
|
||||
putCacheResult(
|
||||
item,
|
||||
previewSize,
|
||||
new CacheResult.Loading(delegateCancellationSignal, callbacks));
|
||||
}
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
/** Clears all cached previews for {@code items}, cancelling any in-progress preview loading. */
|
||||
public void clearPreviews(Iterable<WidgetItem> items) {
|
||||
List<CacheResult> previousCacheResults = new ArrayList<>();
|
||||
synchronized (mCache) {
|
||||
for (WidgetItem item : items) {
|
||||
Map<Size, CacheResult> previousMap = mCache.remove(toComponentKey(item));
|
||||
if (previousMap != null) {
|
||||
previousCacheResults.addAll(previousMap.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (CacheResult previousCacheResult : previousCacheResults) {
|
||||
if (previousCacheResult instanceof CacheResult.Loading) {
|
||||
((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all cached previews, cancelling any in-progress preview loading. */
|
||||
public void clearAll() {
|
||||
List<CacheResult> previousCacheResults;
|
||||
synchronized (mCache) {
|
||||
previousCacheResults =
|
||||
mCache
|
||||
.values()
|
||||
.stream()
|
||||
.flatMap(sizeToResult -> sizeToResult.values().stream())
|
||||
.collect(Collectors.toList());
|
||||
mCache.clear();
|
||||
}
|
||||
|
||||
for (CacheResult previousCacheResult : previousCacheResults) {
|
||||
if (previousCacheResult instanceof CacheResult.Loading) {
|
||||
((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class CacheResult {
|
||||
static final CacheResult MISS = new CacheResult() {};
|
||||
|
||||
static final class Loading extends CacheResult {
|
||||
@NonNull final CancellationSignal mCancellationSignal;
|
||||
@NonNull final Set<WidgetPreviewLoadedCallback> mCallbacks;
|
||||
|
||||
Loading(@NonNull CancellationSignal cancellationSignal,
|
||||
@NonNull Set<WidgetPreviewLoadedCallback> callbacks) {
|
||||
mCancellationSignal = cancellationSignal;
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) {
|
||||
if (mCallbacks.contains(callback)) return this;
|
||||
Set<WidgetPreviewLoadedCallback> newCallbacks =
|
||||
new ArraySet<>(mCallbacks.size() + 1);
|
||||
newCallbacks.addAll(mCallbacks);
|
||||
newCallbacks.add(callback);
|
||||
return new Loading(mCancellationSignal, newCallbacks);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Loading withoutCallback(@NonNull WidgetPreviewLoadedCallback callback) {
|
||||
if (!mCallbacks.contains(callback)) return this;
|
||||
Set<WidgetPreviewLoadedCallback> newCallbacks =
|
||||
new ArraySet<>(mCallbacks.size() - 1);
|
||||
for (WidgetPreviewLoadedCallback existingCallback : mCallbacks) {
|
||||
if (!existingCallback.equals(callback)) {
|
||||
newCallbacks.add(existingCallback);
|
||||
}
|
||||
}
|
||||
return new Loading(mCancellationSignal, newCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Loaded extends CacheResult {
|
||||
@NonNull final Bitmap mBitmap;
|
||||
|
||||
Loaded(@NonNull Bitmap bitmap) {
|
||||
mBitmap = bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ComponentKey toComponentKey(@NonNull WidgetItem item) {
|
||||
return new ComponentKey(item.componentName, item.user);
|
||||
}
|
||||
}
|
||||
+92
-44
@@ -1,4 +1,19 @@
|
||||
package com.android.launcher3;
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
@@ -32,8 +47,14 @@ import android.util.LongSparseArray;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherFiles;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
@@ -47,9 +68,6 @@ import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SQLiteCacheHelper;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.WidgetManagerHelper;
|
||||
import com.android.launcher3.widget.util.WidgetSizes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -60,7 +78,8 @@ import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class WidgetPreviewLoader {
|
||||
/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */
|
||||
public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader {
|
||||
|
||||
private static final String TAG = "WidgetPreviewLoader";
|
||||
private static final boolean DEBUG = false;
|
||||
@@ -80,7 +99,7 @@ public class WidgetPreviewLoader {
|
||||
private final UserCache mUserCache;
|
||||
private final CacheDb mDb;
|
||||
|
||||
public WidgetPreviewLoader(Context context, IconCache iconCache) {
|
||||
public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) {
|
||||
mContext = context;
|
||||
mIconCache = iconCache;
|
||||
mUserCache = UserCache.INSTANCE.get(context);
|
||||
@@ -89,16 +108,24 @@ public class WidgetPreviewLoader {
|
||||
|
||||
/**
|
||||
* Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
|
||||
* called on UI thread
|
||||
* called on UI thread.
|
||||
*
|
||||
* @return a request id which can be used to cancel the request.
|
||||
*/
|
||||
public CancellationSignal getPreview(WidgetItem item, int previewWidth,
|
||||
int previewHeight, WidgetCell caller) {
|
||||
@Override
|
||||
@NonNull
|
||||
public CancellationSignal loadPreview(
|
||||
@NonNull BaseActivity activity,
|
||||
@NonNull WidgetItem item,
|
||||
@NonNull Size previewSize,
|
||||
@NonNull WidgetPreviewLoadedCallback callback) {
|
||||
int previewWidth = previewSize.getWidth();
|
||||
int previewHeight = previewSize.getHeight();
|
||||
String size = previewWidth + "x" + previewHeight;
|
||||
WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
|
||||
|
||||
PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
|
||||
PreviewLoadTask task =
|
||||
new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback);
|
||||
task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
|
||||
|
||||
CancellationSignal signal = new CancellationSignal();
|
||||
@@ -106,6 +133,7 @@ public class WidgetPreviewLoader {
|
||||
return signal;
|
||||
}
|
||||
|
||||
/** Clears the database storing previews. */
|
||||
public void refresh() {
|
||||
mDb.clear();
|
||||
}
|
||||
@@ -126,21 +154,37 @@ public class WidgetPreviewLoader {
|
||||
private static final String COLUMN_VERSION = "version";
|
||||
private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
|
||||
|
||||
public CacheDb(Context context) {
|
||||
CacheDb(Context context) {
|
||||
super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateTable(SQLiteDatabase database) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
|
||||
COLUMN_COMPONENT + " TEXT NOT NULL, " +
|
||||
COLUMN_USER + " INTEGER NOT NULL, " +
|
||||
COLUMN_SIZE + " TEXT NOT NULL, " +
|
||||
COLUMN_PACKAGE + " TEXT NOT NULL, " +
|
||||
COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
|
||||
COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
|
||||
COLUMN_PREVIEW_BITMAP + " BLOB, " +
|
||||
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " +
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS "
|
||||
+ TABLE_NAME
|
||||
+ " ("
|
||||
+ COLUMN_COMPONENT
|
||||
+ " TEXT NOT NULL, "
|
||||
+ COLUMN_USER
|
||||
+ " INTEGER NOT NULL, "
|
||||
+ COLUMN_SIZE
|
||||
+ " TEXT NOT NULL, "
|
||||
+ COLUMN_PACKAGE
|
||||
+ " TEXT NOT NULL, "
|
||||
+ COLUMN_LAST_UPDATED
|
||||
+ " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_VERSION
|
||||
+ " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_PREVIEW_BITMAP
|
||||
+ " BLOB, "
|
||||
+ "PRIMARY KEY ("
|
||||
+ COLUMN_COMPONENT
|
||||
+ ", "
|
||||
+ COLUMN_USER
|
||||
+ ", "
|
||||
+ COLUMN_SIZE
|
||||
+ ") "
|
||||
+
|
||||
");");
|
||||
}
|
||||
}
|
||||
@@ -149,7 +193,7 @@ public class WidgetPreviewLoader {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
|
||||
values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
|
||||
values.put(CacheDb.COLUMN_SIZE, key.size);
|
||||
values.put(CacheDb.COLUMN_SIZE, key.mSize);
|
||||
values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
|
||||
values.put(CacheDb.COLUMN_VERSION, versions[0]);
|
||||
values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
|
||||
@@ -157,12 +201,14 @@ public class WidgetPreviewLoader {
|
||||
mDb.insertOrReplace(values);
|
||||
}
|
||||
|
||||
/** Removes the package from the preview database. */
|
||||
public void removePackage(String packageName, UserHandle user) {
|
||||
removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
|
||||
}
|
||||
|
||||
private void removePackage(String packageName, UserHandle user, long userSerial) {
|
||||
synchronized(mPackageVersions) {
|
||||
/** Removes the package from the preview database. */
|
||||
public void removePackage(String packageName, UserHandle user, long userSerial) {
|
||||
synchronized (mPackageVersions) {
|
||||
mPackageVersions.remove(packageName);
|
||||
}
|
||||
|
||||
@@ -264,7 +310,7 @@ public class WidgetPreviewLoader {
|
||||
new String[]{
|
||||
key.componentName.flattenToShortString(),
|
||||
Long.toString(mUserCache.getSerialNumberForUser(key.user)),
|
||||
key.size
|
||||
key.mSize
|
||||
});
|
||||
// If cancelled, skip getting the blob and decoding it into a bitmap
|
||||
if (loadTask.isCancelled()) {
|
||||
@@ -293,7 +339,7 @@ public class WidgetPreviewLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns generatedPreview for a widget and if the preview should be saved in persistent
|
||||
* Returns a generated preview for a widget and if the preview should be saved in persistent
|
||||
* storage.
|
||||
* @param launcher
|
||||
* @param item
|
||||
@@ -344,8 +390,10 @@ public class WidgetPreviewLoader {
|
||||
if (drawable != null) {
|
||||
drawable = mutateOnMainThread(drawable);
|
||||
} else {
|
||||
Log.w(TAG, "Can't load widget preview drawable 0x" +
|
||||
Integer.toHexString(info.previewImage) + " for provider: " + info.provider);
|
||||
Log.w(TAG, "Can't load widget preview drawable 0x"
|
||||
+ Integer.toHexString(info.previewImage)
|
||||
+ " for provider: "
|
||||
+ info.provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,8 +427,8 @@ public class WidgetPreviewLoader {
|
||||
scale = maxPreviewWidth / (float) (previewWidth);
|
||||
}
|
||||
if (scale != 1f) {
|
||||
previewWidth = Math.max((int)(scale * previewWidth), 1);
|
||||
previewHeight = Math.max((int)(scale * previewHeight), 1);
|
||||
previewWidth = Math.max((int) (scale * previewWidth), 1);
|
||||
previewHeight = Math.max((int) (scale * previewHeight), 1);
|
||||
}
|
||||
|
||||
final Canvas c = new Canvas();
|
||||
@@ -554,13 +602,13 @@ public class WidgetPreviewLoader {
|
||||
}
|
||||
}
|
||||
|
||||
public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
|
||||
private class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
|
||||
implements CancellationSignal.OnCancelListener {
|
||||
@Thunk final WidgetCacheKey mKey;
|
||||
private final WidgetItem mInfo;
|
||||
private final int mPreviewHeight;
|
||||
private final int mPreviewWidth;
|
||||
private final WidgetCell mCaller;
|
||||
private final WidgetPreviewLoadedCallback mCallback;
|
||||
private final BaseActivity mActivity;
|
||||
@Thunk long[] mVersions;
|
||||
@Thunk Bitmap mBitmapToRecycle;
|
||||
@@ -568,14 +616,14 @@ public class WidgetPreviewLoader {
|
||||
@Nullable private Bitmap mUnusedPreviewBitmap;
|
||||
private boolean mSaveToDB = false;
|
||||
|
||||
PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
|
||||
int previewHeight, WidgetCell caller) {
|
||||
PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info,
|
||||
int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) {
|
||||
mActivity = activity;
|
||||
mKey = key;
|
||||
mInfo = info;
|
||||
mPreviewHeight = previewHeight;
|
||||
mPreviewWidth = previewWidth;
|
||||
mCaller = caller;
|
||||
mActivity = BaseActivity.fromContext(mCaller.getContext());
|
||||
mCallback = callback;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format("%s, %s, %d, %d",
|
||||
mKey, mInfo, mPreviewHeight, mPreviewWidth));
|
||||
@@ -593,9 +641,9 @@ public class WidgetPreviewLoader {
|
||||
synchronized (mUnusedBitmaps) {
|
||||
// Check if we can re-use a bitmap
|
||||
for (Bitmap candidate : mUnusedBitmaps) {
|
||||
if (candidate != null && candidate.isMutable() &&
|
||||
candidate.getWidth() == mPreviewWidth &&
|
||||
candidate.getHeight() == mPreviewHeight) {
|
||||
if (candidate != null && candidate.isMutable()
|
||||
&& candidate.getWidth() == mPreviewWidth
|
||||
&& candidate.getHeight() == mPreviewHeight) {
|
||||
unusedBitmap = candidate;
|
||||
mUnusedBitmaps.remove(unusedBitmap);
|
||||
break;
|
||||
@@ -638,7 +686,7 @@ public class WidgetPreviewLoader {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Bitmap preview) {
|
||||
mCaller.applyPreview(preview);
|
||||
mCallback.onPreviewLoaded(preview);
|
||||
|
||||
// Write the generated preview to the DB in the worker thread
|
||||
if (mVersions != null) {
|
||||
@@ -716,21 +764,21 @@ public class WidgetPreviewLoader {
|
||||
|
||||
private static final class WidgetCacheKey extends ComponentKey {
|
||||
|
||||
@Thunk final String size;
|
||||
@Thunk final String mSize;
|
||||
|
||||
public WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
|
||||
WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
|
||||
super(componentName, user);
|
||||
this.size = size;
|
||||
this.mSize = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode() ^ size.hashCode();
|
||||
return super.hashCode() ^ mSize.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o) && ((WidgetCacheKey) o).size.equals(size);
|
||||
return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,20 @@
|
||||
|
||||
package com.android.launcher3.widget;
|
||||
|
||||
import static com.android.launcher3.Utilities.getBoundsForViewInDragLayer;
|
||||
import static com.android.launcher3.Utilities.setRect;
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Advanceable;
|
||||
@@ -43,7 +37,6 @@ import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.CheckLongPressHelper;
|
||||
import com.android.launcher3.Launcher;
|
||||
@@ -51,9 +44,9 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
|
||||
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
|
||||
@@ -63,7 +56,7 @@ import java.util.List;
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
|
||||
implements TouchCompleteListener, View.OnLongClickListener,
|
||||
LocalColorExtractor.Listener {
|
||||
|
||||
@@ -78,8 +71,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
// Maximum duration for which updates can be deferred.
|
||||
private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
|
||||
|
||||
protected final LayoutInflater mInflater;
|
||||
|
||||
private final CheckLongPressHelper mLongPressHelper;
|
||||
protected final Launcher mLauncher;
|
||||
private final Workspace mWorkspace;
|
||||
@@ -102,21 +93,9 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
private final Rect mCurrentWidgetSize = new Rect();
|
||||
private final Rect mWidgetSizeAtDrag = new Rect();
|
||||
|
||||
private final float[] mTmpFloatArray = new float[4];
|
||||
private final RectF mTempRectF = new RectF();
|
||||
private final Rect mEnforcedRectangle = new Rect();
|
||||
private final float mEnforcedCornerRadius;
|
||||
private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
|
||||
outline.setEmpty();
|
||||
} else {
|
||||
outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final Object mUpdateLock = new Object();
|
||||
private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper;
|
||||
private long mDeferUpdatesUntilMillis = 0;
|
||||
private RemoteViews mMostRecentRemoteViews;
|
||||
|
||||
@@ -125,18 +104,16 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
mWorkspace = mLauncher.getWorkspace();
|
||||
mLongPressHelper = new CheckLongPressHelper(this, this);
|
||||
mInflater = LayoutInflater.from(context);
|
||||
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
|
||||
setBackgroundResource(R.drawable.widget_internal_focus_bg);
|
||||
|
||||
setExecutor(Executors.THREAD_POOL_EXECUTOR);
|
||||
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
|
||||
setOnLightBackground(true);
|
||||
}
|
||||
mColorExtractor = LocalColorExtractor.newInstance(getContext());
|
||||
mColorExtractor.setListener(this);
|
||||
|
||||
mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
|
||||
mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,11 +143,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getErrorView() {
|
||||
return mInflater.inflate(R.layout.appwidget_error, this, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAppWidget(RemoteViews remoteViews) {
|
||||
synchronized (mUpdateLock) {
|
||||
@@ -305,40 +277,17 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
}
|
||||
}
|
||||
|
||||
public void switchToErrorView() {
|
||||
// Update the widget with 0 Layout id, to reset the view to error view.
|
||||
updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
try {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
} catch (final RuntimeException e) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
switchToErrorView();
|
||||
}
|
||||
});
|
||||
}
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
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(), (View) getParent(),
|
||||
mCurrentWidgetSize, true, mTmpFloatArray, mTempRectF);
|
||||
setRect(mTempRectF, mCurrentWidgetSize);
|
||||
mDragLayerRelativeCoordinateHelper.viewToRect(this, mCurrentWidgetSize);
|
||||
updateColorExtraction(mCurrentWidgetSize,
|
||||
mWorkspace.getPageIndexForScreenId(info.screenId));
|
||||
}
|
||||
|
||||
enforceRoundedCorners();
|
||||
}
|
||||
|
||||
/** Starts the drag mode. */
|
||||
@@ -358,7 +307,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
mIsInDragMode = false;
|
||||
mDragListener = null;
|
||||
mWidgetSizeAtDrag.setEmpty();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -510,40 +458,4 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void resetRoundedCorners() {
|
||||
setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
setClipToOutline(false);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void enforceRoundedCorners() {
|
||||
if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
|
||||
resetRoundedCorners();
|
||||
return;
|
||||
}
|
||||
View background = RoundedCornerEnforcement.findBackground(this);
|
||||
if (background == null
|
||||
|| RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
|
||||
resetRoundedCorners();
|
||||
return;
|
||||
}
|
||||
RoundedCornerEnforcement.computeRoundedRectangle(this,
|
||||
background,
|
||||
mEnforcedRectangle);
|
||||
setOutlineProvider(mCornerRadiusEnforcementOutline);
|
||||
setClipToOutline(true);
|
||||
}
|
||||
|
||||
/** Returns the corner radius currently enforced, in pixels. */
|
||||
public float getEnforcedCornerRadius() {
|
||||
return mEnforcedCornerRadius;
|
||||
}
|
||||
|
||||
/** Returns true if the corner radius are enforced for this App Widget. */
|
||||
public boolean hasEnforcedCornerRadius() {
|
||||
return getClipToOutline();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.launcher3.widget;
|
||||
import static com.android.launcher3.Utilities.ATLEAST_S;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -44,7 +43,6 @@ import com.android.launcher3.BaseActivity;
|
||||
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.FastBitmapDrawable;
|
||||
import com.android.launcher3.icons.RoundDrawableWrapper;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
@@ -222,21 +220,18 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ATLEAST_S
|
||||
&& mRemoteViewsPreview == null
|
||||
&& item.widgetInfo != null
|
||||
&& item.widgetInfo.previewLayout != Resources.ID_NULL) {
|
||||
mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
|
||||
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
|
||||
LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
|
||||
item.widgetInfo.clone());
|
||||
// A hack to force the initial layout to be the preview layout since there is no API for
|
||||
// rendering a preview layout for work profile apps yet. For non-work profile layout, a
|
||||
// proper solution is to use RemoteViews(PackageName, LayoutId).
|
||||
launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
|
||||
setAppWidgetHostViewPreview(mAppWidgetHostViewPreview,
|
||||
launcherAppWidgetProviderInfo, /* remoteViews= */ null);
|
||||
}
|
||||
if (!item.hasPreviewLayout()) return;
|
||||
|
||||
mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
|
||||
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
|
||||
LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
|
||||
item.widgetInfo.clone());
|
||||
// A hack to force the initial layout to be the preview layout since there is no API for
|
||||
// rendering a preview layout for work profile apps yet. For non-work profile layout, a
|
||||
// proper solution is to use RemoteViews(PackageName, LayoutId).
|
||||
launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
|
||||
setAppWidgetHostViewPreview(mAppWidgetHostViewPreview,
|
||||
launcherAppWidgetProviderInfo, /* remoteViews= */ null);
|
||||
}
|
||||
|
||||
private void setAppWidgetHostViewPreview(
|
||||
@@ -344,22 +339,25 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
|
||||
if (mActiveRequest != null) {
|
||||
return;
|
||||
}
|
||||
mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, mPreviewWidth, mPreviewHeight,
|
||||
this);
|
||||
mActiveRequest = mWidgetPreviewLoader.loadPreview(
|
||||
BaseActivity.fromContext(getContext()), mItem,
|
||||
new Size(mPreviewWidth, mPreviewHeight),
|
||||
this::applyPreview);
|
||||
}
|
||||
|
||||
/** Sets the widget preview image size in number of cells. */
|
||||
public void setPreviewSize(int spanX, int spanY) {
|
||||
setPreviewSize(spanX, spanY, 1f);
|
||||
public Size setPreviewSize(int spanX, int spanY) {
|
||||
return setPreviewSize(spanX, spanY, 1f);
|
||||
}
|
||||
|
||||
/** Sets the widget preview image size, in number of cells, and preview scale. */
|
||||
public void setPreviewSize(int spanX, int spanY, float previewScale) {
|
||||
public Size setPreviewSize(int spanX, int spanY, float previewScale) {
|
||||
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
|
||||
Size widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, spanX, spanY);
|
||||
mPreviewWidth = widgetSize.getWidth();
|
||||
mPreviewHeight = widgetSize.getHeight();
|
||||
mPreviewScale = previewScale;
|
||||
return widgetSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
|
||||
/** Asynchronous loader of preview bitmaps for {@link WidgetItem}s. */
|
||||
public interface WidgetPreviewLoader {
|
||||
/**
|
||||
* Loads a widget preview and calls back to {@code callback} when complete.
|
||||
*
|
||||
* @return a {@link CancellationSignal} which can be used to cancel the request.
|
||||
*/
|
||||
@NonNull
|
||||
@UiThread
|
||||
CancellationSignal loadPreview(
|
||||
@NonNull BaseActivity activity,
|
||||
@NonNull WidgetItem item,
|
||||
@NonNull Size previewSize,
|
||||
@NonNull WidgetPreviewLoadedCallback callback);
|
||||
|
||||
/** Callback class for requests to {@link WidgetPreviewLoader}. */
|
||||
interface WidgetPreviewLoadedCallback {
|
||||
void onPreviewLoaded(@NonNull Bitmap preview);
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,19 @@ public abstract class WidgetsListBaseEntry {
|
||||
@Rank
|
||||
public abstract int getRank();
|
||||
|
||||
/**
|
||||
* Marker interface for subclasses that are headers for widget list items.
|
||||
*
|
||||
* @param <T> The type of this class.
|
||||
*/
|
||||
public interface Header<T extends WidgetsListBaseEntry & Header<T>> {
|
||||
/** Returns whether the widget list is currently expanded. */
|
||||
boolean isWidgetListShown();
|
||||
|
||||
/** Returns a copy of the item with the widget list shown. */
|
||||
T withWidgetListShown();
|
||||
}
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
|
||||
public @interface Rank {
|
||||
|
||||
@@ -21,41 +21,33 @@ import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import java.util.List;
|
||||
|
||||
/** An information holder for an app which has widgets or/and shortcuts. */
|
||||
public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
|
||||
public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry
|
||||
implements WidgetsListBaseEntry.Header<WidgetsListHeaderEntry> {
|
||||
|
||||
public final int widgetsCount;
|
||||
public final int shortcutsCount;
|
||||
|
||||
private boolean mIsWidgetListShown = false;
|
||||
private boolean mHasEntryUpdated = false;
|
||||
private final boolean mIsWidgetListShown;
|
||||
|
||||
public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
|
||||
List<WidgetItem> items) {
|
||||
this(pkgItem, titleSectionName, items, /* isWidgetListShown= */ false);
|
||||
}
|
||||
|
||||
private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
|
||||
List<WidgetItem> items, boolean isWidgetListShown) {
|
||||
super(pkgItem, titleSectionName, items);
|
||||
widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count();
|
||||
shortcutsCount = Math.max(0, items.size() - widgetsCount);
|
||||
}
|
||||
|
||||
/** Sets if the widgets list associated with this header is shown. */
|
||||
public void setIsWidgetListShown(boolean isWidgetListShown) {
|
||||
if (mIsWidgetListShown != isWidgetListShown) {
|
||||
this.mIsWidgetListShown = isWidgetListShown;
|
||||
mHasEntryUpdated = true;
|
||||
} else {
|
||||
mHasEntryUpdated = false;
|
||||
}
|
||||
mIsWidgetListShown = isWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the widgets list associated with this header is shown. */
|
||||
@Override
|
||||
public boolean isWidgetListShown() {
|
||||
return mIsWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if this entry has been updated due to user interactions. */
|
||||
public boolean hasEntryUpdated() {
|
||||
return mHasEntryUpdated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Header:" + mPkgItem.packageName + ":" + mWidgets.size();
|
||||
@@ -72,6 +64,18 @@ public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
|
||||
if (!(obj instanceof WidgetsListHeaderEntry)) return false;
|
||||
WidgetsListHeaderEntry otherEntry = (WidgetsListHeaderEntry) obj;
|
||||
return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
|
||||
&& mTitleSectionName.equals(otherEntry.mTitleSectionName);
|
||||
&& mTitleSectionName.equals(otherEntry.mTitleSectionName)
|
||||
&& mIsWidgetListShown == otherEntry.mIsWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns a copy of this {@link WidgetsListHeaderEntry} with the widget list shown. */
|
||||
@Override
|
||||
public WidgetsListHeaderEntry withWidgetListShown() {
|
||||
if (mIsWidgetListShown) return this;
|
||||
return new WidgetsListHeaderEntry(
|
||||
mPkgItem,
|
||||
mTitleSectionName,
|
||||
mWidgets,
|
||||
/* isWidgetListShown= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,36 +21,28 @@ import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import java.util.List;
|
||||
|
||||
/** An information holder for an app which has widgets or/and shortcuts, to be shown in search. */
|
||||
public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry {
|
||||
public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry
|
||||
implements WidgetsListBaseEntry.Header<WidgetsListSearchHeaderEntry> {
|
||||
|
||||
private boolean mIsWidgetListShown = false;
|
||||
private boolean mHasEntryUpdated = false;
|
||||
private final boolean mIsWidgetListShown;
|
||||
|
||||
public WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
|
||||
List<WidgetItem> items) {
|
||||
super(pkgItem, titleSectionName, items);
|
||||
this(pkgItem, titleSectionName, items, /* isWidgetListShown= */ false);
|
||||
}
|
||||
|
||||
/** Sets if the widgets list associated with this header is shown. */
|
||||
public void setIsWidgetListShown(boolean isWidgetListShown) {
|
||||
if (mIsWidgetListShown != isWidgetListShown) {
|
||||
this.mIsWidgetListShown = isWidgetListShown;
|
||||
mHasEntryUpdated = true;
|
||||
} else {
|
||||
mHasEntryUpdated = false;
|
||||
}
|
||||
private WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
|
||||
List<WidgetItem> items, boolean isWidgetListShown) {
|
||||
super(pkgItem, titleSectionName, items);
|
||||
mIsWidgetListShown = isWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the widgets list associated with this header is shown. */
|
||||
@Override
|
||||
public boolean isWidgetListShown() {
|
||||
return mIsWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if this entry has been updated due to user interactions. */
|
||||
public boolean hasEntryUpdated() {
|
||||
return mHasEntryUpdated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHeader:" + mPkgItem.packageName + ":" + mWidgets.size();
|
||||
@@ -67,6 +59,18 @@ public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry {
|
||||
if (!(obj instanceof WidgetsListSearchHeaderEntry)) return false;
|
||||
WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj;
|
||||
return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
|
||||
&& mTitleSectionName.equals(otherEntry.mTitleSectionName);
|
||||
&& mTitleSectionName.equals(otherEntry.mTitleSectionName)
|
||||
&& mIsWidgetListShown;
|
||||
}
|
||||
|
||||
/** Returns a copy of this {@link WidgetsListSearchHeaderEntry} with the widget list shown. */
|
||||
@Override
|
||||
public WidgetsListSearchHeaderEntry withWidgetListShown() {
|
||||
if (mIsWidgetListShown) return this;
|
||||
return new WidgetsListSearchHeaderEntry(
|
||||
mPkgItem,
|
||||
mTitleSectionName,
|
||||
mWidgets,
|
||||
/* isWidgetListShown= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public class WidgetsDiffReporter {
|
||||
*/
|
||||
private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) {
|
||||
if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) {
|
||||
return ((WidgetsListHeaderEntry) newRow).hasEntryUpdated() || !curRow.equals(newRow);
|
||||
return !curRow.equals(newRow);
|
||||
}
|
||||
if (newRow instanceof WidgetsListSearchHeaderEntry
|
||||
&& curRow instanceof WidgetsListSearchHeaderEntry) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -35,19 +36,25 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
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;
|
||||
import com.android.launcher3.widget.CachingWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
|
||||
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.util.WidgetSizes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -79,7 +86,9 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> 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;
|
||||
|
||||
private final Context mContext;
|
||||
private final Launcher mLauncher;
|
||||
private final CachingWidgetPreviewLoader mCachingPreviewLoader;
|
||||
private final WidgetsDiffReporter mDiffReporter;
|
||||
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
|
||||
private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
|
||||
@@ -97,16 +106,23 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
.equals(mWidgetsContentVisiblePackageUserKey);
|
||||
@Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
|
||||
@Nullable private RecyclerView mRecyclerView;
|
||||
@Nullable private PackageUserKey mPendingClickHeader;
|
||||
private int mShortcutPreviewPadding;
|
||||
|
||||
private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
|
||||
ignored -> updateVisibleEntries();
|
||||
|
||||
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
|
||||
WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
|
||||
DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
|
||||
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
|
||||
mContext = context;
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader);
|
||||
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
|
||||
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
|
||||
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
|
||||
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
|
||||
layoutInflater, iconClickListener, iconLongClickListener,
|
||||
widgetPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
|
||||
mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
|
||||
mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
|
||||
mViewHolderBinders.put(
|
||||
VIEW_TYPE_WIDGETS_HEADER,
|
||||
@@ -122,6 +138,9 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
/* onHeaderClickListener= */ this,
|
||||
listDrawableFactory,
|
||||
/* listAdapter= */ this));
|
||||
mShortcutPreviewPadding =
|
||||
2 * context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,6 +196,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
|
||||
/** Updates the widget list based on {@code tempEntries}. */
|
||||
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
|
||||
mCachingPreviewLoader.clearAll();
|
||||
mAllEntries = tempEntries.stream().sorted(mRowComparator)
|
||||
.collect(Collectors.toList());
|
||||
if (shouldClearVisibleEntries()) {
|
||||
@@ -189,36 +209,110 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
|
||||
// Forget the expanded package every time widget list is refreshed in search mode.
|
||||
mWidgetsContentVisiblePackageUserKey = null;
|
||||
cancelLoadingPreviews();
|
||||
setWidgets(searchResults);
|
||||
}
|
||||
|
||||
private void updateVisibleEntries() {
|
||||
mAllEntries.forEach(entry -> {
|
||||
if (entry instanceof WidgetsListHeaderEntry) {
|
||||
((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
|
||||
isHeaderForVisibleContent(entry));
|
||||
} else if (entry instanceof WidgetsListSearchHeaderEntry) {
|
||||
((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown(
|
||||
isHeaderForVisibleContent(entry));
|
||||
}
|
||||
});
|
||||
// If not all previews are ready, then defer this update and try again after the preview
|
||||
// loads.
|
||||
if (!ensureAllPreviewsReady()) return;
|
||||
|
||||
// Get the current top of the header with the matching key before adjusting the visible
|
||||
// entries.
|
||||
OptionalInt previousPositionForPackageUserKey =
|
||||
getPositionForPackageUserKey(mPendingClickHeader);
|
||||
OptionalInt topForPackageUserKey =
|
||||
getOffsetForPosition(previousPositionForPackageUserKey);
|
||||
|
||||
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
|
||||
.filter(entry -> (mFilter == null || mFilter.test(entry))
|
||||
&& mHeaderAndSelectedContentFilter.test(entry))
|
||||
.map(entry -> {
|
||||
// Adjust the original entries to expand headers for the selected content.
|
||||
if (entry instanceof WidgetsListBaseEntry.Header<?>
|
||||
&& matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
|
||||
return ((WidgetsListBaseEntry.Header<?>) entry).withWidgetListShown();
|
||||
}
|
||||
return entry;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
|
||||
|
||||
if (mPendingClickHeader != null) {
|
||||
// Get the position for the clicked header after adjusting the visible entries. The
|
||||
// position may have changed if another header had previously been expanded.
|
||||
OptionalInt positionForPackageUserKey =
|
||||
getPositionForPackageUserKey(mPendingClickHeader);
|
||||
scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey);
|
||||
mPendingClickHeader = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether {@code entry} matches {@link #mWidgetsContentVisiblePackageUserKey}. */
|
||||
private boolean isHeaderForVisibleContent(WidgetsListBaseEntry entry) {
|
||||
return isHeaderForPackageUserKey(entry, mWidgetsContentVisiblePackageUserKey);
|
||||
/**
|
||||
* Checks that all preview images are loaded and starts loading for those that aren't ready.
|
||||
*
|
||||
* @return true if all previews are ready and the data can be updated, false otherwise.
|
||||
*/
|
||||
private boolean ensureAllPreviewsReady() {
|
||||
boolean allReady = true;
|
||||
BaseActivity activity = BaseActivity.fromContext(mContext);
|
||||
for (WidgetsListBaseEntry entry : mAllEntries) {
|
||||
if (!(entry instanceof WidgetsListContentEntry)) continue;
|
||||
|
||||
WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry;
|
||||
if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
|
||||
// If the entry isn't visible, clear any loaded previews.
|
||||
mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.mWidgets.size(); i++) {
|
||||
WidgetItem widgetItem = entry.mWidgets.get(i);
|
||||
DeviceProfile deviceProfile = activity.getDeviceProfile();
|
||||
Size widgetSize =
|
||||
WidgetSizes.getWidgetSizePx(
|
||||
deviceProfile,
|
||||
widgetItem.spanX,
|
||||
widgetItem.spanY);
|
||||
if (widgetItem.isShortcut()) {
|
||||
widgetSize =
|
||||
new Size(
|
||||
widgetSize.getWidth() + mShortcutPreviewPadding,
|
||||
widgetSize.getHeight() + mShortcutPreviewPadding);
|
||||
}
|
||||
|
||||
if (widgetItem.hasPreviewLayout()
|
||||
|| mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) {
|
||||
// The widget is ready if it can be rendered with a preview layout or if its
|
||||
// preview bitmap is in the cache.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we've reached this point, we should load the preview for the widget.
|
||||
allReady = false;
|
||||
mCachingPreviewLoader.loadPreview(
|
||||
activity,
|
||||
widgetItem,
|
||||
widgetSize,
|
||||
mPreviewLoadedCallback);
|
||||
}
|
||||
}
|
||||
return allReady;
|
||||
}
|
||||
|
||||
/** Returns whether {@code entry} matches {@code key}. */
|
||||
private boolean isHeaderForPackageUserKey(WidgetsListBaseEntry entry, PackageUserKey key) {
|
||||
return (entry instanceof WidgetsListHeaderEntry
|
||||
|| entry instanceof WidgetsListSearchHeaderEntry)
|
||||
&& new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user).equals(key);
|
||||
private static boolean isHeaderForPackageUserKey(
|
||||
@NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) {
|
||||
return entry instanceof WidgetsListBaseEntry.Header && matchesKey(entry, key);
|
||||
}
|
||||
|
||||
private static boolean matchesKey(
|
||||
@NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) {
|
||||
if (key == null) return false;
|
||||
return entry.mPkgItem.packageName.equals(key.mPackageName)
|
||||
&& entry.mPkgItem.user.equals(key.mUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,6 +321,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
public void resetExpandedHeader() {
|
||||
if (mWidgetsContentVisiblePackageUserKey != null) {
|
||||
mWidgetsContentVisiblePackageUserKey = null;
|
||||
cancelLoadingPreviews();
|
||||
updateVisibleEntries();
|
||||
}
|
||||
}
|
||||
@@ -285,6 +380,8 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
// Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
|
||||
if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
|
||||
|
||||
cancelLoadingPreviews();
|
||||
|
||||
if (showWidgets) {
|
||||
mWidgetsContentVisiblePackageUserKey = packageUserKey;
|
||||
mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
|
||||
@@ -292,17 +389,15 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
mWidgetsContentVisiblePackageUserKey = null;
|
||||
}
|
||||
|
||||
// Get the current top of the header with the matching key before adjusting the visible
|
||||
// entries.
|
||||
OptionalInt topForPackageUserKey =
|
||||
getOffsetForPosition(getPositionForPackageUserKey(packageUserKey));
|
||||
// Store the header that was clicked so that its position will be maintained the next time
|
||||
// we update the entries.
|
||||
mPendingClickHeader = packageUserKey;
|
||||
|
||||
updateVisibleEntries();
|
||||
}
|
||||
|
||||
// Get the position for the clicked header after adjusting the visible entries. The
|
||||
// position may have changed if another header had previously been expanded.
|
||||
OptionalInt positionForPackageUserKey = getPositionForPackageUserKey(packageUserKey);
|
||||
scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey);
|
||||
private void cancelLoadingPreviews() {
|
||||
mCachingPreviewLoader.clearAll();
|
||||
}
|
||||
|
||||
/** Returns the position of the currently expanded header, or empty if it's not present. */
|
||||
@@ -315,7 +410,8 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
* Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not
|
||||
* present.
|
||||
*/
|
||||
private OptionalInt getPositionForPackageUserKey(PackageUserKey key) {
|
||||
@NonNull
|
||||
private OptionalInt getPositionForPackageUserKey(@Nullable PackageUserKey key) {
|
||||
return IntStream.range(0, mVisibleEntries.size())
|
||||
.filter(index -> isHeaderForPackageUserKey(mVisibleEntries.get(index), key))
|
||||
.findFirst();
|
||||
|
||||
@@ -18,8 +18,9 @@ package com.android.launcher3.widget.picker;
|
||||
import static com.android.launcher3.widget.picker.WidgetsListDrawableState.LAST;
|
||||
import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -30,9 +31,9 @@ import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WidgetPreviewLoader;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.recyclerview.ViewHolderBinder;
|
||||
import com.android.launcher3.widget.CachingWidgetPreviewLoader;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
import com.android.launcher3.widget.util.WidgetsTableUtils;
|
||||
@@ -52,17 +53,16 @@ public final class WidgetsListTableViewHolderBinder
|
||||
private final LayoutInflater mLayoutInflater;
|
||||
private final OnClickListener mIconClickListener;
|
||||
private final OnLongClickListener mIconLongClickListener;
|
||||
private final WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private final WidgetsListDrawableFactory mListDrawableFactory;
|
||||
private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private final WidgetsListAdapter mWidgetsListAdapter;
|
||||
private boolean mApplyBitmapDeferred = false;
|
||||
|
||||
public WidgetsListTableViewHolderBinder(
|
||||
Context context,
|
||||
LayoutInflater layoutInflater,
|
||||
OnClickListener iconClickListener,
|
||||
OnLongClickListener iconLongClickListener,
|
||||
WidgetPreviewLoader widgetPreviewLoader,
|
||||
CachingWidgetPreviewLoader widgetPreviewLoader,
|
||||
WidgetsListDrawableFactory listDrawableFactory,
|
||||
WidgetsListAdapter listAdapter) {
|
||||
mLayoutInflater = layoutInflater;
|
||||
@@ -75,7 +75,7 @@ public final class WidgetsListTableViewHolderBinder
|
||||
|
||||
/**
|
||||
* Defers applying bitmap on all the {@link WidgetCell} at
|
||||
* {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry)} if
|
||||
* {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry, int)} if
|
||||
* {@code applyBitmapDeferred} is {@code true}.
|
||||
*/
|
||||
public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
|
||||
@@ -124,10 +124,15 @@ public final class WidgetsListTableViewHolderBinder
|
||||
WidgetCell widget = (WidgetCell) row.getChildAt(j);
|
||||
widget.clear();
|
||||
WidgetItem widgetItem = widgetItemsPerRow.get(j);
|
||||
widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
|
||||
Size previewSize = widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
|
||||
widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
|
||||
widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
|
||||
widget.ensurePreview();
|
||||
Bitmap preview = mWidgetPreviewLoader.getPreview(widgetItem, previewSize);
|
||||
if (preview == null) {
|
||||
widget.ensurePreview();
|
||||
} else {
|
||||
widget.applyPreview(preview);
|
||||
}
|
||||
widget.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +99,11 @@ public final class WidgetSizes {
|
||||
*/
|
||||
public static Bundle getWidgetSizeOptions(Context context, ComponentName provider, int spanX,
|
||||
int spanY) {
|
||||
boolean shouldInsetWidgets =
|
||||
LauncherAppState.getIDP(context).getDeviceProfile(context).shouldInsetWidgets();
|
||||
ArrayList<SizeF> sizes = getWidgetSizes(context, spanX, spanY);
|
||||
Rect padding = getDefaultPaddingForWidget(context, provider, null);
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
float xPaddingDips = shouldInsetWidgets ? (padding.left + padding.right) / density : 0;
|
||||
float yPaddingDips = shouldInsetWidgets ? (padding.top + padding.bottom) / density : 0;
|
||||
float xPaddingDips = (padding.left + padding.right) / density;
|
||||
float yPaddingDips = (padding.top + padding.bottom) / density;
|
||||
|
||||
ArrayList<SizeF> paddedSizes = sizes.stream()
|
||||
.map(size -> new SizeF(
|
||||
|
||||
@@ -127,7 +127,7 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd
|
||||
@Override
|
||||
public void setActiveMarker(int activePage) {
|
||||
updateTabTextColor(activePage);
|
||||
updateIndicatorPosition(activePage);
|
||||
updateIndicatorPosition(mIsRtl ? 1 - activePage : activePage);
|
||||
if (mOnActivePageChangedListener != null && mLastActivePage != activePage) {
|
||||
mOnActivePageChangedListener.onActivePageChanged(activePage);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.DeadObjectException;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
@@ -269,6 +270,9 @@ public final class LauncherInstrumentation {
|
||||
try (ContentProviderClient client = getContext().getContentResolver()
|
||||
.acquireContentProviderClient(mTestProviderUri)) {
|
||||
return client.call(request, null, null);
|
||||
} catch (DeadObjectException e) {
|
||||
fail("Launcher crashed");
|
||||
return null;
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -185,8 +185,12 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
|
||||
targetAppSelector);
|
||||
if (headerTitle != null) {
|
||||
// If we find the header and it has not been expanded, let's click it to see the
|
||||
// widgets list.
|
||||
if (!hasHeaderExpanded) {
|
||||
// widgets list. Note that we wait until the header is out of the gesture region at
|
||||
// the bottom of the screen, because tapping there in Launcher3 causes NexusLauncher
|
||||
// to briefly appear to handle the gesture, which can break our test.
|
||||
boolean isHeaderOutOfGestureRegion = headerTitle.getVisibleCenter().y
|
||||
< mLauncher.getBottomGestureStartOnScreen();
|
||||
if (!hasHeaderExpanded && isHeaderOutOfGestureRegion) {
|
||||
log("Header has not been expanded. Click to expand.");
|
||||
hasHeaderExpanded = true;
|
||||
mLauncher.clickLauncherObject(headerTitle);
|
||||
|
||||
Reference in New Issue
Block a user