Merge branch 'ub-launcher3-dorval-polish2'

Bug: 64937210
Test: flashed img
Change-Id: Id6a05e7be885bf5c53a41eab2573c6ae3a44dc55
This commit is contained in:
Jon Miranda
2017-09-18 16:53:16 -04:00
82 changed files with 1039 additions and 368 deletions
+1
View File
@@ -82,6 +82,7 @@
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
</activity>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* Copyright 2017, 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.
*/
-->
<resources>
<bool name="notification_badging_enabled">false</bool>
</resources>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2017 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.
*/
-->
<resources>
<!-- String representing the intent to delete a package. -->
<string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string>
</resources>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2017, 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.
*/
-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2166"
android:repeatCount="5">
<propertyValuesHolder
android:propertyName="progress"
android:valueType="floatType">
<keyframe
android:fraction="0"
android:value="1f" />
<keyframe
android:fraction="0.346"
android:value="1f" />
<keyframe
android:fraction=".423"
android:interpolator="@interpolator/disco_bounce"
android:value="0.9438f" />
<keyframe
android:fraction="0.654"
android:interpolator="@interpolator/disco_bounce"
android:value="1f" />
<keyframe
android:fraction="1"
android:value="1f" />
</propertyValuesHolder>
</objectAnimator>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2016, The Android Open Source Project
** Copyright 2017, 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.
@@ -16,30 +16,20 @@
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially"
android:startOffset="200">
android:ordering="sequentially">
<objectAnimator
android:duration="166"
android:interpolator="@interpolator/disco_bounce"
android:propertyName="progress"
android:duration="250"
android:interpolator="@interpolator/disco_bounce_section1"
android:startOffset="750"
android:valueFrom="1f"
android:valueTo=".94f"
android:valueType="floatType"/>
android:valueTo="0.9438f"
android:valueType="floatType" />
<objectAnimator
android:duration="500"
android:interpolator="@interpolator/disco_bounce"
android:propertyName="progress"
android:duration="216"
android:interpolator="@interpolator/disco_bounce_section2"
android:valueFrom=".94f"
android:valueTo="1.012f"
android:valueType="floatType"/>
<objectAnimator
android:propertyName="progress"
android:duration="234"
android:interpolator="@interpolator/disco_bounce_section3"
android:valueFrom="1.012f"
android:valueTo="1f"
android:valueType="floatType"/>
android:valueType="floatType" />
</set>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/profile_badge_size"
android:height="@dimen/profile_badge_size"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:fillColor="@android:color/black"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/white"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/white"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/black"
android:fillAlpha="0.87"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
</vector>
@@ -16,7 +16,7 @@
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.9"
android:controlY1="0"
android:controlX2="0.5"
android:controlY2="1"/>
android:controlX1="0.35"
android:controlY1="0"
android:controlX2="0.5"
android:controlY2="1" />
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 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
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.9"
android:controlY1="0"
android:controlX2="0.6"
android:controlY2="1"/>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 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
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.1"
android:controlY1="0"
android:controlX2="0.7"
android:controlY2="1"/>
+2 -3
View File
@@ -68,11 +68,10 @@
</com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
<View
style="@style/AllAppsNavBarProtection"
android:id="@+id/nav_bar_bg"
android:background="?attr/allAppsNavBarScrimColor"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:focusable="false"
android:visibility="invisible" />
android:focusable="false" />
</com.android.launcher3.allapps.AllAppsContainerView>
+2 -2
View File
@@ -49,7 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:gravity="bottom"
android:gravity="center_vertical"
android:text="@string/notifications_header"
android:textSize="@dimen/notification_header_text_size"
android:textColor="?android:attr/textColorPrimary" />
@@ -58,7 +58,7 @@
android:layout_width="@dimen/notification_icon_size"
android:layout_height="match_parent"
android:layout_gravity="end"
android:gravity="bottom|center_horizontal"
android:gravity="center"
android:textSize="@dimen/notification_header_count_text_size"
android:fontFamily="sans-serif-medium"
android:textColor="?android:attr/textColorPrimary" />
+2 -2
View File
@@ -31,7 +31,7 @@
android:background="?attr/popupColorPrimary"
android:paddingStart="@dimen/notification_padding_start"
android:paddingEnd="@dimen/notification_main_text_padding_end"
android:paddingBottom="16dp">
android:paddingBottom="14dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -59,7 +59,7 @@
android:layout_width="@dimen/notification_icon_size"
android:layout_height="@dimen/notification_icon_size"
android:layout_marginEnd="@dimen/notification_padding_end"
android:layout_marginBottom="8dp"
android:layout_marginBottom="7dp"
android:layout_gravity="center_vertical|end" />
</com.android.launcher3.notification.NotificationMainView>
-6
View File
@@ -26,10 +26,4 @@
<item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
</style>
<!-- From O and above, we show a dark nav bar in all-apps -->
<style name="AllAppsNavBarProtection">
<item name="android:alpha">0.6</item>
<item name="android:background">?android:attr/colorPrimary</item>
</style>
</resources>
+1
View File
@@ -20,6 +20,7 @@
<!-- Attributes used for launcher theme -->
<attr name="allAppsScrimColor" format="color" />
<attr name="allAppsNavBarScrimColor" format="color" />
<attr name="popupColorPrimary" format="color" />
<attr name="popupColorSecondary" format="color" />
<attr name="popupColorTertiary" format="color" />
+8 -2
View File
@@ -1,7 +1,7 @@
<resources>
<!-- Dynamic Grid -->
<!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
<integer name="config_dynamic_grid_overview_icon_zone_percentage">20</integer>
<integer name="config_dynamic_grid_overview_icon_zone_percentage">22</integer>
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
@@ -9,7 +9,7 @@
<bool name="is_large_tablet">false</bool>
<bool name="allow_rotation">false</bool>
<integer name="extracted_color_gradient_alpha">191</integer>
<integer name="extracted_color_gradient_alpha">153</integer>
<!-- A string pointer to the original app name string. This allows derived projects to
easily override the app name without providing all translations -->
@@ -19,6 +19,9 @@
q=<query> to the data to the intent -->
<string name="market_search_intent" translatable="false">market://search?c=apps</string>
<!-- String representing the intent to delete a package.-->
<string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
<!-- Values for icon shape overrides. These should correspond to entries defined
in icon_shape_override_paths_names -->
<string-array translatable="false" name="icon_shape_override_paths_values">
@@ -113,6 +116,9 @@
<!-- Name of a color extraction implementation class. -->
<string name="color_extraction_impl_class" translatable="false"></string>
<!-- Name of a subclass of com.android.launcher3.util.InstantAppResolver. Can be empty. -->
<string name="instant_app_resolver_class" translatable="false"></string>
<!-- Package name of the default wallpaper picker. -->
<string name="wallpaper_picker_package" translatable="false"></string>
+4 -2
View File
@@ -214,9 +214,11 @@
<dimen name="notification_padding_end">12dp</dimen>
<!-- notification_padding_end + (icon_size - footer_icon_size) / 2 -->
<dimen name="notification_footer_icon_row_padding">15dp</dimen>
<dimen name="notification_header_height">32dp</dimen>
<dimen name="notification_main_height">96dp</dimen>
<dimen name="notification_header_height">36dp</dimen>
<dimen name="notification_main_height">84dp</dimen>
<dimen name="notification_footer_height">32dp</dimen>
<!-- How much space to keep as padding for the last notification when the footer collapses -->
<dimen name="notification_empty_footer_height">6dp</dimen>
<dimen name="notification_header_text_size">13sp</dimen>
<dimen name="notification_header_count_text_size">12sp</dimen>
<dimen name="notification_main_title_size">16sp</dimen>
+4 -8
View File
@@ -29,7 +29,8 @@
</style>
<style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
<item name="allAppsScrimColor">#DDFFFFFF</item>
<item name="allAppsScrimColor">#CCFFFFFF</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
<item name="popupColorPrimary">#FFF</item>
<item name="popupColorSecondary">#F5F5F5</item> <!-- Gray 100 -->
<item name="popupColorTertiary">#E0E0E0</item> <!-- Gray 300 -->
@@ -61,7 +62,8 @@
<item name="android:textColorHint">#A0FFFFFF</item>
<item name="android:colorControlHighlight">#A0FFFFFF</item>
<item name="android:colorPrimary">#FF333333</item>
<item name="allAppsScrimColor">#33000000</item>
<item name="allAppsScrimColor">#7A212121</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
<item name="popupColorPrimary">?android:attr/colorPrimary</item>
<item name="popupColorSecondary">#424242</item> <!-- Gray 800 -->
<item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
@@ -103,12 +105,6 @@
<item name="android:includeFontPadding">false</item>
</style>
<!-- Style for nav bar background in all-apps screen -->
<style name="AllAppsNavBarProtection">
<item name="android:alpha">?android:attr/spotShadowAlpha</item>
<item name="android:background">@color/default_shadow_color_no_alpha</item>
</style>
<!-- Base theme for BubbleTextView and sub classes -->
<style name="BaseIcon">
<item name="android:layout_width">match_parent</item>
+7 -3
View File
@@ -95,10 +95,14 @@ public class AppInfo extends ItemInfoWithIcon {
}
public static Intent makeLaunchIntent(LauncherActivityInfo info) {
return makeLaunchIntent(info.getComponentName());
}
public static Intent makeLaunchIntent(ComponentName cn) {
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(info.getComponentName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(cn)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
@Override
@@ -55,7 +55,6 @@ public class AppWidgetResizeFrame extends FrameLayout
private final int[] mDirectionVector = new int[2];
private final int[] mLastDirectionVector = new int[2];
private final int[] mTmpPt = new int[2];
private final IntRange mTempRange1 = new IntRange();
private final IntRange mTempRange2 = new IntRange();
@@ -344,13 +343,12 @@ public class AppWidgetResizeFrame extends FrameLayout
return rect;
}
/**
* This is the final step of the resize. Here we save the new widget size and position
* to LauncherModel and animate the resize frame.
*/
public void commitResize() {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// We are done with resizing the widget. Save the widget size & position to LauncherModel
resizeWidgetIfNeeded(true);
requestLayout();
}
private void onTouchUp() {
@@ -184,7 +184,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) {
applyIconAndLabel(info.iconBitmap, info);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
if (promiseStateChanged || (info.hasPromiseIconUi())) {
applyPromiseState(promiseStateChanged);
}
@@ -481,7 +481,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
public void applyPromiseState(boolean promiseStateChanged) {
if (getTag() instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) getTag();
final boolean isPromise = info.isPromise();
final boolean isPromise = info.hasPromiseIconUi();
final int progressLevel = isPromise ?
((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
info.getInstallProgress() : 0)) : 100;
@@ -29,6 +29,7 @@ import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -69,6 +70,7 @@ public abstract class ButtonDropTarget extends TextView
/** The paint applied to the drag view on hover */
protected int mHoverColor = 0;
protected CharSequence mText;
protected ColorStateList mOriginalTextColor;
protected Drawable mDrawable;
@@ -96,6 +98,7 @@ public abstract class ButtonDropTarget extends TextView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mText = getText();
mOriginalTextColor = getTextColors();
}
@@ -297,4 +300,30 @@ public abstract class ButtonDropTarget extends TextView
public int getTextColor() {
return getTextColors().getDefaultColor();
}
/**
* Returns True if any update was made.
*/
public boolean updateText(boolean hide) {
if ((hide && getText().toString().isEmpty()) || (!hide && mText.equals(getText()))) {
return false;
}
setText(hide ? "" : mText);
return true;
}
public boolean isTextTruncated() {
int availableWidth = getMeasuredWidth();
if (mHideParentOnDisable) {
ViewGroup parent = (ViewGroup) getParent();
availableWidth = parent.getMeasuredWidth() - parent.getPaddingLeft()
- parent.getPaddingRight();
}
availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
+ getCompoundDrawablePadding());
CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth,
TextUtils.TruncateAt.END);
return !mText.equals(displayedText);
}
}
@@ -65,9 +65,11 @@ public class DeleteDropTarget extends ButtonDropTarget {
* Set the drop target's text to either "Remove" or "Cancel" depending on the drag source.
*/
public void setTextBasedOnDragSource(DragSource dragSource) {
if (!TextUtils.isEmpty(getText())) {
setText(dragSource.supportsDeleteDropTarget() ? R.string.remove_drop_target_label
if (!TextUtils.isEmpty(mText)) {
mText = getResources().getString(dragSource.supportsDeleteDropTarget()
? R.string.remove_drop_target_label
: android.R.string.cancel);
requestLayout();
}
}
+9 -4
View File
@@ -267,6 +267,10 @@ public class DeviceProfile {
}
DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
// We take the minimum sizes of this profile and it's multi-window variant to ensure that
// the system decor is always excluded.
mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
// In multi-window mode, we can have widthPx = availableWidthPx
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
// widthPx and heightPx values where it's needed.
@@ -555,9 +559,9 @@ public class DeviceProfile {
int getOverviewModeButtonBarHeight() {
int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
return zoneHeight;
return Utilities.boundToRange(zoneHeight,
overviewModeMinIconZoneHeightPx,
overviewModeMaxIconZoneHeightPx);
}
public static int calculateCellWidth(int width, int countX) {
@@ -693,7 +697,8 @@ public class DeviceProfile {
lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
lp.width = Math.min(availableWidthPx, maxWidth);
lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
lp.height = getOverviewModeButtonBarHeight();
lp.bottomMargin = mInsets.bottom;
overviewMode.setLayoutParams(lp);
}
@@ -78,6 +78,58 @@ public class DropTargetBar extends LinearLayout implements DragController.DragLi
setupButtonDropTarget(this, dragController);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean hideText = hideTextHelper(false /* shouldUpdateText */, false /* no-op */);
if (hideTextHelper(true /* shouldUpdateText */, hideText)) {
// Text has changed, so we need to re-measure.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
/**
* Helper method that iterates through the children and returns whether any of the visible
* {@link ButtonDropTarget} has truncated text.
*
* @param shouldUpdateText If True, updates the text of all children.
* @param hideText If True and {@param shouldUpdateText} is True, clears the text of all
* children; otherwise it sets the original text value.
*
*
* @return If shouldUpdateText is True, returns whether any of the children updated their text.
* Else, returns whether any of the children have truncated their text.
*/
private boolean hideTextHelper(boolean shouldUpdateText, boolean hideText) {
boolean result = false;
View visibleView;
ButtonDropTarget dropTarget;
for (int i = getChildCount() - 1; i >= 0; --i) {
if (getChildAt(i) instanceof ButtonDropTarget) {
visibleView = dropTarget = (ButtonDropTarget) getChildAt(i);
} else if (getChildAt(i) instanceof ViewGroup) {
// The Drop Target is wrapped in a FrameLayout.
visibleView = getChildAt(i);
dropTarget = (ButtonDropTarget) ((ViewGroup) visibleView).getChildAt(0);
} else {
// Ignore other views.
continue;
}
if (visibleView.getVisibility() == View.VISIBLE) {
if (shouldUpdateText) {
result |= dropTarget.updateText(hideText);
} else if (dropTarget.isTextTruncated()) {
result = true;
break;
}
}
}
return result;
}
private void setupButtonDropTarget(View view, DragController dragController) {
if (view instanceof ButtonDropTarget) {
ButtonDropTarget bdt = (ButtonDropTarget) view;
-1
View File
@@ -149,7 +149,6 @@ public class Hotseat extends FrameLayout
allAppsButton.setOnKeyListener(new HotseatIconKeyEventListener());
if (mLauncher != null) {
mLauncher.setAllAppsButton(allAppsButton);
allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
allAppsButton.setOnClickListener(mLauncher);
allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
}
+11 -2
View File
@@ -47,6 +47,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Provider;
import com.android.launcher3.util.SQLiteCacheHelper;
@@ -94,6 +95,7 @@ public class IconCache {
private final LauncherAppsCompat mLauncherApps;
private final HashMap<ComponentKey, CacheEntry> mCache =
new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
private final InstantAppResolver mInstantAppResolver;
private final int mIconDpi;
@Thunk final IconDB mIconDb;
@@ -106,6 +108,7 @@ public class IconCache {
mPackageManager = context.getPackageManager();
mUserManager = UserManagerCompat.getInstance(mContext);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
mIconDpi = inv.fillResIconDpi;
mIconDb = new IconDB(context, inv.iconBitmapSize);
@@ -120,7 +123,7 @@ public class IconCache {
}
private Drawable getFullResDefaultActivityIcon() {
return getFullResIcon(Resources.getSystem(), Utilities.isAtLeastO() ?
return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ?
android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon);
}
@@ -575,7 +578,6 @@ public class IconCache {
// For icon caching, do not go through DB. Just update the in-memory entry.
if (entry == null) {
entry = new CacheEntry();
mCache.put(cacheKey, entry);
}
if (!TextUtils.isEmpty(title)) {
entry.title = title;
@@ -583,6 +585,9 @@ public class IconCache {
if (icon != null) {
entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
}
if (!TextUtils.isEmpty(title) && entry.icon != null) {
mCache.put(cacheKey, entry);
}
}
private static ComponentKey getPackageKey(String packageName, UserHandle user) {
@@ -619,6 +624,10 @@ public class IconCache {
// only keep the low resolution icon instead of the larger full-sized icon
Bitmap icon = LauncherIcons.createBadgedIconBitmap(
appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
if (mInstantAppResolver.isInstantApp(appInfo)) {
icon = LauncherIcons.badgeWithDrawable(icon,
mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
}
Bitmap lowResIcon = generateLowResIcon(icon);
entry.title = appInfo.loadLabel(mPackageManager);
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+2 -1
View File
@@ -2,6 +2,7 @@ package com.android.launcher3;
import android.content.pm.LauncherActivityInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import java.util.Locale;
@@ -17,7 +18,7 @@ public class IconProvider {
}
public void updateSystemStateString() {
mSystemState = Locale.getDefault().toString();
mSystemState = Locale.getDefault().toString() + "," + Build.VERSION.SDK_INT;
}
public String getIconSystemState(String packageName) {
@@ -75,7 +75,7 @@ public class InfoDropTarget extends UninstallDropTarget {
if (info instanceof AppInfo) {
componentName = ((AppInfo) info).componentName;
} else if (info instanceof ShortcutInfo) {
componentName = ((ShortcutInfo) info).intent.getComponent();
componentName = info.getTargetComponent();
} else if (info instanceof PendingAddItemInfo) {
componentName = ((PendingAddItemInfo) info).componentName;
} else if (info instanceof LauncherAppWidgetInfo) {
+3 -9
View File
@@ -138,17 +138,11 @@ public class ItemInfo {
public ComponentName getTargetComponent() {
Intent intent = getIntent();
if (intent == null) {
if (intent != null) {
return intent.getComponent();
} else {
return null;
}
ComponentName cn = intent.getComponent();
if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT && cn == null) {
// Legacy shortcuts may not have a componentName but just a packageName. In that case
// create a dummy componentName instead of adding additional check everywhere.
String pkg = intent.getPackage();
return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
}
return cn;
}
public void writeToValues(ContentWriter writer) {
+6 -39
View File
@@ -113,7 +113,6 @@ import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -121,6 +120,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.RunnableWithId;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
@@ -272,7 +272,6 @@ public class Launcher extends BaseActivity
private IconCache mIconCache;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
private final Handler mHandler = new Handler();
private boolean mIsResumeFromActionScreenOff;
private boolean mHasFocus = false;
private ObjectAnimator mScrimAnimator;
@@ -280,8 +279,6 @@ public class Launcher extends BaseActivity
private PopupDataProvider mPopupDataProvider;
private View.OnTouchListener mHapticFeedbackTouchListener;
// Determines how long to wait after a rotation before restoring the screen orientation to
// match the sensor state.
private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
@@ -1046,7 +1043,6 @@ public class Launcher extends BaseActivity
if (shouldShowDiscoveryBounce()) {
mAllAppsController.showDiscoveryBounce();
}
mIsResumeFromActionScreenOff = false;
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onResume();
}
@@ -1134,7 +1130,7 @@ public class Launcher extends BaseActivity
// On O and above we there is always some setting present settings (add icon to
// home screen or icon badging). On earlier APIs we will have the allow rotation
// setting, on devices with a locked orientation,
return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation);
return Utilities.ATLEAST_OREO || !getResources().getBoolean(R.bool.allow_rotation);
}
}
@@ -1328,7 +1324,6 @@ public class Launcher extends BaseActivity
onClickWallpaperPicker(view);
}
}.attachTo(wallpaperButton);
wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind widget button actions
mWidgetsButton = findViewById(R.id.widget_button);
@@ -1338,7 +1333,6 @@ public class Launcher extends BaseActivity
onClickAddWidgetButton(view);
}
}.attachTo(mWidgetsButton);
mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind settings actions
View settingsButton = findViewById(R.id.settings_button);
@@ -1350,7 +1344,6 @@ public class Launcher extends BaseActivity
onClickSettingsButton(view);
}
}.attachTo(settingsButton);
settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
} else {
settingsButton.setVisibility(View.GONE);
}
@@ -1416,7 +1409,7 @@ public class Launcher extends BaseActivity
CellLayout layout = getCellLayout(container, screenId);
ShortcutInfo info = null;
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
info = LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest(
this, LauncherAppsCompatVO.getPinItemRequest(data), 0);
}
@@ -1548,7 +1541,6 @@ public class Launcher extends BaseActivity
mAppsView.reset();
}
}
mIsResumeFromActionScreenOff = true;
mShouldFadeInScrim = true;
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
// ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
@@ -2424,7 +2416,7 @@ public class Launcher extends BaseActivity
}
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.isPromise()) {
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
String packageName = shortcut.intent.getComponent() != null ?
shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
if (!TextUtils.isEmpty(packageName)) {
@@ -2536,22 +2528,6 @@ public class Launcher extends BaseActivity
startActivity(intent, getActivityLaunchOptions(v));
}
public View.OnTouchListener getHapticFeedbackTouchListener() {
if (mHapticFeedbackTouchListener == null) {
mHapticFeedbackTouchListener = new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
return false;
}
};
}
return mHapticFeedbackTouchListener;
}
@Override
public void onAccessibilityStateChanged(boolean enabled) {
mDragLayer.onAccessibilityStateChanged(enabled);
@@ -3068,7 +3044,7 @@ public class Launcher extends BaseActivity
*/
public void tryAndUpdatePredictedApps() {
if (mLauncherCallbacks != null) {
List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
List<ComponentKeyMapper<AppInfo>> apps = mLauncherCallbacks.getPredictedApps();
if (apps != null) {
mAppsView.setPredictedApps(apps);
}
@@ -3910,16 +3886,7 @@ public class Launcher extends BaseActivity
}
private boolean shouldShowDiscoveryBounce() {
if (mState != State.WORKSPACE) {
return false;
}
if (mLauncherCallbacks != null && mLauncherCallbacks.shouldShowDiscoveryBounce()) {
return true;
}
if (!mIsResumeFromActionScreenOff) {
return false;
}
return !mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false);
return mState == State.WORKSPACE && !mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false);
}
protected void moveWorkspaceToDefaultScreen() {
@@ -92,7 +92,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView
setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
setBackgroundResource(R.drawable.widget_internal_focus_bg);
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
setExecutor(Utilities.THREAD_POOL_EXECUTOR);
}
}
@@ -21,7 +21,7 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -91,10 +91,8 @@ public interface LauncherCallbacks {
*/
boolean shouldMoveToDefaultScreenOnHomeIntent();
boolean hasSettings();
List<ComponentKey> getPredictedApps();
List<ComponentKeyMapper<AppInfo>> getPredictedApps();
int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1;
/** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */
int getSearchBarHeight();
boolean shouldShowDiscoveryBounce();
}
@@ -59,7 +59,7 @@ public class SessionCommitReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!isEnabled(context) || !Utilities.isAtLeastO()) {
if (!isEnabled(context) || !Utilities.ATLEAST_OREO) {
// User has decided to not add icons on homescreen.
return;
}
@@ -92,7 +92,7 @@ public class SessionCommitReceiver extends BroadcastReceiver {
}
public static void applyDefaultUserPrefs(final Context context) {
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
return;
}
SharedPreferences prefs = Utilities.getPrefs(context);
@@ -94,10 +94,12 @@ public class SettingsActivity extends Activity {
ButtonPreference iconBadgingPref =
(ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
getPreferenceScreen().removePreference(
findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
getPreferenceScreen().removePreference(iconBadgingPref);
} else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) {
getPreferenceScreen().removePreference(iconBadgingPref);
} else {
// Listen to system notification badge settings while this UI is active.
mIconBadgingObserver = new IconBadgingObserver(
@@ -17,6 +17,7 @@
package com.android.launcher3;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
@@ -58,6 +59,11 @@ public class ShortcutInfo extends ItemInfoWithIcon {
*/
public static final int FLAG_RESTORE_STARTED = 8; //0B1000;
/**
* Web UI supported.
*/
public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
/**
* Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
* Upto 15 different types supported.
@@ -188,6 +194,10 @@ public class ShortcutInfo extends ItemInfoWithIcon {
return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON);
}
public boolean hasPromiseIconUi() {
return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
}
public int getInstallProgress() {
return mInstallProgress;
}
@@ -226,4 +236,18 @@ public class ShortcutInfo extends ItemInfoWithIcon {
public boolean isDisabled() {
return isDisabled != 0;
}
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a dummy componentName instead of adding additional
// check everywhere.
String pkg = intent.getPackage();
return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
}
return cn;
}
}
@@ -11,12 +11,17 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import com.android.launcher3.compat.LauncherAppsCompat;
import java.net.URISyntaxException;
public class UninstallDropTarget extends ButtonDropTarget {
private static final String TAG = "UninstallDropTarget";
public UninstallDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -99,25 +104,28 @@ public class UninstallDropTarget extends ButtonDropTarget {
final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
final ComponentName cn = getUninstallTarget(launcher, info);
final boolean isUninstallable;
boolean canUninstall;
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
isUninstallable = false;
canUninstall = false;
} else {
Intent intent = new Intent(Intent.ACTION_DELETE,
Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_USER, info.user);
launcher.startActivity(intent);
isUninstallable = true;
try {
Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0)
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, info.user);
launcher.startActivity(i);
canUninstall = true;
} catch (URISyntaxException e) {
Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
canUninstall = false;
}
}
if (callback != null) {
sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
sendUninstallResult(launcher, canUninstall, cn, info.user, callback);
}
return isUninstallable;
return canUninstall;
}
/**
+7 -5
View File
@@ -83,15 +83,17 @@ public final class Utilities {
private static final Matrix sMatrix = new Matrix();
private static final Matrix sInverseMatrix = new Matrix();
public static boolean isAtLeastO() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public static final boolean ATLEAST_OREO_MR1 =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
public static final boolean ATLEAST_OREO =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
public static final boolean ATLEAST_NOUGAT_MR1 =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
public static final boolean ATLEAST_NOUGAT =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
public static final boolean ATLEAST_MARSHMALLOW =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
@@ -50,6 +50,7 @@ import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import com.android.launcher3.util.PackageUserKey;
import java.util.List;
@@ -116,7 +117,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
/**
* Sets the current set of predicted apps.
*/
public void setPredictedApps(List<ComponentKey> apps) {
public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
mApps.setPredictedApps(apps);
}
@@ -349,8 +350,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
navBarBgLp.height = insets.bottom;
navBarBg.setLayoutParams(navBarBgLp);
navBarBg.setVisibility(FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS
? View.INVISIBLE : View.VISIBLE);
}
}
@@ -448,9 +448,20 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
row = Math.abs(numTotalRows - row);
}
// We manipulate the stiffness, min, and max values based on the items distance to the
// first row and the items distance to the center column to create the ^-shaped motion
// effect.
calculateSpringValues(spring, row, col);
}
@Override
public void setDefaultValues(SpringAnimation spring) {
calculateSpringValues(spring, 0, mAppsPerRow / 2);
}
/**
* We manipulate the stiffness, min, and max values based on the items distance to the
* first row and the items distance to the center column to create the ^-shaped motion
* effect.
*/
private void calculateSpringValues(SpringAnimation spring, int row, int col) {
float rowFactor = (1 + row) * 0.5f;
float colFactor = getColumnFactor(col, mAppsPerRow);
@@ -7,6 +7,7 @@ import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.support.animation.SpringAnimation;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.MotionEvent;
@@ -98,9 +99,10 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect
// Used in discovery bounce animation to provide the transition without workspace changing.
private boolean mIsTranslateWithoutWorkspace = false;
private AnimatorSet mDiscoBounceAnimation;
private Animator mDiscoBounceAnimation;
private GradientView mGradientView;
private SpringAnimation mSearchSpring;
private SpringAnimationHandler mSpringAnimationHandler;
public AllAppsTransitionController(Launcher l) {
@@ -226,6 +228,7 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect
}
mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */);
if (hasSpringAnimationHandler()) {
mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
// The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
}
@@ -418,8 +421,8 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect
cancelDiscoveryAnimation();
// assumption is that this variable is always null
mDiscoBounceAnimation = (AnimatorSet) AnimatorInflater.loadAnimator(mLauncher,
R.anim.discovery_bounce);
mDiscoBounceAnimation = AnimatorInflater.loadAnimator(mLauncher,
R.animator.discovery_bounce);
mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
@@ -499,6 +502,7 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect
public void finishPullUp() {
mHotseat.setVisibility(View.INVISIBLE);
if (hasSpringAnimationHandler()) {
mSpringAnimationHandler.remove(mSearchSpring);
mSpringAnimationHandler.reset();
}
setProgress(0f);
@@ -544,6 +548,7 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect
mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher);
mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
mSpringAnimationHandler = mAppsView.getSpringAnimationHandler();
mSearchSpring = mAppsView.getSearchUiManager().getSpringForFling();
}
private boolean hasSpringAnimationHandler() {
@@ -29,6 +29,7 @@ import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import com.android.launcher3.util.LabelComparator;
import java.util.ArrayList;
@@ -173,7 +174,7 @@ public class AlphabeticalAppsList {
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
private final List<AppInfo> mPredictedApps = new ArrayList<>();
private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
@@ -298,20 +299,20 @@ public class AlphabeticalAppsList {
updateAdapterItems();
}
private List<AppInfo> processPredictedAppComponents(List<ComponentKey> components) {
private List<AppInfo> processPredictedAppComponents(List<ComponentKeyMapper<AppInfo>> components) {
if (mComponentToAppMap.isEmpty()) {
// Apps have not been bound yet.
return Collections.emptyList();
}
List<AppInfo> predictedApps = new ArrayList<>();
for (ComponentKey ck : components) {
AppInfo info = mComponentToAppMap.get(ck);
for (ComponentKeyMapper<AppInfo> mapper : components) {
AppInfo info = mapper.getItem(mComponentToAppMap);
if (info != null) {
predictedApps.add(info);
} else {
if (FeatureFlags.IS_DOGFOOD_BUILD) {
Log.e(TAG, "Predicted app not found: " + ck);
Log.e(TAG, "Predicted app not found: " + mapper);
}
}
// Stop at the number of predicted apps
@@ -331,7 +332,7 @@ public class AlphabeticalAppsList {
* If the number of predicted apps is the same as the previous list of predicted apps,
* we can optimize by swapping them in place.
*/
public void setPredictedApps(List<ComponentKey> apps) {
public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps);
@@ -472,14 +473,14 @@ public class AlphabeticalAppsList {
if (DEBUG_PREDICTIONS) {
if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle()));
mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle())));
mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle())));
mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle())));
mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName,
Process.myUserHandle())));
}
}
@@ -644,8 +645,8 @@ public class AlphabeticalAppsList {
return result;
}
public AppInfo findApp(ComponentKey key) {
return mComponentToAppMap.get(key);
public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) {
return mapper.getItem(mComponentToAppMap);
}
/**
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
import android.support.animation.SpringAnimation;
import android.support.annotation.NonNull;
import android.view.KeyEvent;
/**
@@ -27,6 +29,11 @@ public interface SearchUiManager {
*/
void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView);
/**
* A {@link SpringAnimation} that will be used when the user flings.
*/
@NonNull SpringAnimation getSpringForFling();
/**
* Notifies the search manager that the apps-list has changed and the search UI should be
* updated accordingly.
@@ -17,6 +17,9 @@ package com.android.launcher3.allapps.search;
import android.content.Context;
import android.graphics.Rect;
import android.support.animation.FloatValueHolder;
import android.support.animation.SpringAnimation;
import android.support.animation.SpringForce;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Selection;
@@ -62,6 +65,8 @@ public class AppsSearchContainerLayout extends FrameLayout
private View mDivider;
private HeaderElevationController mElevationController;
private SpringAnimation mSpring;
public AppsSearchContainerLayout(Context context) {
this(context, null);
}
@@ -81,6 +86,9 @@ public class AppsSearchContainerLayout extends FrameLayout
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
// Note: This spring does nothing.
mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
}
@Override
@@ -126,6 +134,11 @@ public class AppsSearchContainerLayout extends FrameLayout
new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this);
}
@Override
public @NonNull SpringAnimation getSpringForFling() {
return mSpring;
}
@Override
public void refreshSearchResult() {
mSearchBarController.refreshSearchResult();
@@ -69,6 +69,20 @@ public class SpringAnimationHandler<T> {
mAnimationFactory = factory;
}
/**
* Adds a spring to the list of springs handled by this class.
* @param spring The new spring to be added.
* @param setDefaultValues If True, sets the spring to the default
* {@link AnimationFactory} values.
*/
public void add(SpringAnimation spring, boolean setDefaultValues) {
if (setDefaultValues) {
mAnimationFactory.setDefaultValues(spring);
}
spring.setStartVelocity(mCurrentVelocity);
mAnimations.add(spring);
}
/**
* Adds a new or recycled animation to the list of springs handled by this class.
*
@@ -82,15 +96,17 @@ public class SpringAnimationHandler<T> {
view.setTag(R.id.spring_animation_tag, spring);
}
mAnimationFactory.update(spring, object);
spring.setStartVelocity(mCurrentVelocity);
mAnimations.add(spring);
add(spring, false /* setDefaultValues */);
}
/**
* Stops and removes the spring attached to {@param view}.
*/
public void remove(View view) {
SpringAnimation animation = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
remove((SpringAnimation) view.getTag(R.id.spring_animation_tag));
}
public void remove(SpringAnimation animation) {
if (animation.canSkipToEnd()) {
animation.skipToEnd();
}
@@ -226,6 +242,11 @@ public class SpringAnimationHandler<T> {
* Updates the value of {@param spring} based on {@param object}.
*/
void update(SpringAnimation spring, T object);
/**
* Sets the factory default values for the given {@param spring}.
*/
void setDefaultValues(SpringAnimation spring);
}
/**
@@ -40,7 +40,7 @@ public abstract class AppWidgetManagerCompat {
public static AppWidgetManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
sInstance = new AppWidgetManagerCompatVO(context.getApplicationContext());
} else {
sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
@@ -53,7 +53,7 @@ public abstract class LauncherAppsCompat {
public static LauncherAppsCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
sInstance = new LauncherAppsCompatVO(context.getApplicationContext());
} else {
sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
@@ -31,10 +31,10 @@ public abstract class WallpaperManagerCompat {
if (sInstance == null) {
context = context.getApplicationContext();
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
try {
sInstance = new WallpaperManagerCompatVOMR1(context);
} catch (Exception e) {
} catch (Throwable e) {
// The wallpaper APIs do not yet exist
}
}
@@ -16,96 +16,70 @@
package com.android.launcher3.compat;
import android.annotation.TargetApi;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.WallpaperManager.OnColorsChangedListener;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@TargetApi(Build.VERSION_CODES.O)
@TargetApi(27)
public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat {
private static final String TAG = "WMCompatVOMR1";
private final WallpaperManager mWm;
private Method mWCColorHintsMethod;
private final Class mOCLClass;
private final Method mAddOCLMethod;
private final Method mWCGetMethod;
private final Method mWCGetPrimaryColorMethod;
private final Method mWCGetSecondaryColorMethod;
private final Method mWCGetTertiaryColorMethod;
private final Method mWCColorHintsMethod;
WallpaperManagerCompatVOMR1(Context context) throws Exception {
WallpaperManagerCompatVOMR1(Context context) throws Throwable {
mWm = context.getSystemService(WallpaperManager.class);
mOCLClass = Class.forName("android.app.WallpaperManager$OnColorsChangedListener");
mAddOCLMethod = WallpaperManager.class.getDeclaredMethod(
"addOnColorsChangedListener", mOCLClass, Handler.class);
mWCGetMethod = WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class);
Class wallpaperColorsClass = mWCGetMethod.getReturnType();
mWCGetPrimaryColorMethod = wallpaperColorsClass.getDeclaredMethod("getPrimaryColor");
mWCGetSecondaryColorMethod = wallpaperColorsClass.getDeclaredMethod("getSecondaryColor");
mWCGetTertiaryColorMethod = wallpaperColorsClass.getDeclaredMethod("getTertiaryColor");
mWCColorHintsMethod = wallpaperColorsClass.getDeclaredMethod("getColorHints");
String className = WallpaperColors.class.getName();
try {
mWCColorHintsMethod = WallpaperColors.class.getDeclaredMethod("getColorHints");
} catch (Exception exc) {
Log.e(TAG, "getColorHints not available", exc);
}
}
@Nullable
@Override
public WallpaperColorsCompat getWallpaperColors(int which) {
try {
return convertColorsObject(mWCGetMethod.invoke(mWm, which));
} catch (Exception e) {
Log.e(TAG, "Error calling wallpaper API", e);
return null;
}
return convertColorsObject(mWm.getWallpaperColors(which));
}
@Override
public void addOnColorsChangedListener(final OnColorsChangedListenerCompat listener) {
Object onChangeListener = Proxy.newProxyInstance(
WallpaperManager.class.getClassLoader(),
new Class[]{mOCLClass},
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects)
throws Throwable {
String methodName = method.getName();
if ("onColorsChanged".equals(methodName)) {
listener.onColorsChanged(
convertColorsObject(objects[0]), (Integer) objects[1]);
} else if ("toString".equals(methodName)) {
return listener.toString();
}
return null;
}
});
try {
mAddOCLMethod.invoke(mWm, onChangeListener, null);
} catch (Exception e) {
Log.e(TAG, "Error calling wallpaper API", e);
}
OnColorsChangedListener onChangeListener = new OnColorsChangedListener() {
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
listener.onColorsChanged(convertColorsObject(colors), which);
}
};
mWm.addOnColorsChangedListener(onChangeListener, null);
}
private WallpaperColorsCompat convertColorsObject(Object colors) throws Exception {
private WallpaperColorsCompat convertColorsObject(WallpaperColors colors) {
if (colors == null) {
return null;
}
Color primary = (Color) mWCGetPrimaryColorMethod.invoke(colors);
Color secondary = (Color) mWCGetSecondaryColorMethod.invoke(colors);
Color tertiary = (Color) mWCGetTertiaryColorMethod.invoke(colors);
Color primary = colors.getPrimaryColor();
Color secondary = colors.getSecondaryColor();
Color tertiary = colors.getTertiaryColor();
int primaryVal = primary != null ? primary.toArgb() : 0;
int secondaryVal = secondary != null ? secondary.toArgb() : 0;
int tertiaryVal = tertiary != null ? tertiary.toArgb() : 0;
int colorHints = (Integer) mWCColorHintsMethod.invoke(colors);
int colorHints = 0;
try {
if (mWCColorHintsMethod != null) {
colorHints = (Integer) mWCColorHintsMethod.invoke(colors);
}
} catch (Exception exc) {
Log.e(TAG, "error calling color hints", exc);
}
return new WallpaperColorsCompat(primaryVal, secondaryVal, tertiaryVal, colorHints);
}
}
@@ -56,8 +56,6 @@ abstract class BaseFlags {
public static final boolean PULLDOWN_SEARCH = false;
// When enabled the status bar may show dark icons based on the top of the wallpaper.
public static final boolean LIGHT_STATUS_BAR = false;
// When enabled icons are badged with the number of notifications associated with that app.
public static final boolean BADGE_ICONS = true;
// When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
public static final boolean LEGACY_ICON_TREATMENT = true;
// When enabled, adaptive icons would have shadows baked when being stored to icon cache.
@@ -544,7 +544,6 @@ public class DragLayer extends InsettableFrameLayout {
public void clearResizeFrame() {
if (mCurrentResizeFrame != null) {
mCurrentResizeFrame.commitResize();
removeView(mCurrentResizeFrame);
mCurrentResizeFrame = null;
}
@@ -193,7 +193,7 @@ public class DragView extends View {
*/
@TargetApi(Build.VERSION_CODES.O)
public void setItemInfo(final ItemInfo info) {
if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.isAtLeastO())) {
if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.ATLEAST_OREO)) {
return;
}
if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
@@ -128,7 +128,7 @@ public class PinItemDragListener extends BaseItemDragListener implements Parcela
}
public static boolean handleDragRequest(Launcher launcher, Intent intent) {
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
return false;
}
if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
@@ -25,7 +25,9 @@ import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.IconCache;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
@@ -69,8 +71,12 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
@Override
public Drawable getFullResIcon(IconCache cache) {
return mContext.getSystemService(LauncherApps.class)
Drawable d = mContext.getSystemService(LauncherApps.class)
.getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
if (d == null) {
d = new FastBitmapDrawable(cache.getDefaultIcon(Process.myUserHandle()));
}
return d;
}
@Override
@@ -61,6 +61,7 @@ import com.android.launcher3.Workspace;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.BaseItemDragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.graphics.IconPalette;
@@ -375,6 +376,9 @@ public class FolderIcon extends FrameLayout implements FolderListener {
if (d.dragInfo instanceof AppInfo) {
// Came from all apps -- make a copy
item = ((AppInfo) d.dragInfo).makeShortcut();
} else if (d.dragSource instanceof BaseItemDragListener){
// Came from a different window -- make a copy
item = new ShortcutInfo((ShortcutInfo) d.dragInfo);
} else {
item = (ShortcutInfo) d.dragInfo;
}
@@ -80,7 +80,7 @@ public class DrawableFactory {
protected Path getPreloadProgressPath(Context context) {
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
try {
// Try to load the path from Mask Icon
Drawable icon = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
@@ -231,7 +231,7 @@ public class IconNormalizer {
*/
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
@Nullable Path path, @Nullable boolean[] outMaskShape) {
if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
if (outBounds != null) {
outBounds.set(mAdaptiveIconBounds);
@@ -347,7 +347,7 @@ public class IconNormalizer {
float areaScale = area / (width * height);
// Use sqrt of the final ratio as the images is scaled across both width and height.
float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = scale;
mAdaptiveIconBounds.set(mBounds);
@@ -59,7 +59,7 @@ public class IconShapeOverride {
private static final int RESTART_REQUEST_CODE = 42; // the answer to everything
public static boolean isSupported(Context context) {
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
return false;
}
// Only supported when developer settings is enabled
@@ -82,7 +82,7 @@ public class IconShapeOverride {
}
public static void apply(Context context) {
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
return;
}
String path = getAppliedValue(context);
@@ -38,6 +38,7 @@ import android.os.UserHandle;
import android.support.annotation.Nullable;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.IconCache;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
@@ -103,7 +104,7 @@ public class LauncherIcons {
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
@@ -122,7 +123,7 @@ public class LauncherIcons {
}
}
Bitmap bitmap = createIconBitmap(icon, context, scale);
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
icon instanceof AdaptiveIconDrawable) {
bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
}
@@ -157,13 +158,13 @@ public class LauncherIcons {
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
dr.setBounds(0, 0, 1, 1);
scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT &&
if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
!outShape[0]) {
Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
if (wrappedIcon != icon) {
@@ -192,13 +193,16 @@ public class LauncherIcons {
* Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
*/
public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
return badgeWithDrawable(srcTgt, new FastBitmapDrawable(badge), context);
}
public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) {
int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
synchronized (sCanvas) {
sCanvas.setBitmap(srcTgt);
sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
new Rect(srcTgt.getWidth() - badgeSize,
srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
new Paint(Paint.FILTER_BITMAP_FLAG));
int iconSize = srcTgt.getWidth();
badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
badge.draw(sCanvas);
sCanvas.setBitmap(null);
}
return srcTgt;
@@ -209,12 +213,12 @@ public class LauncherIcons {
*/
public static Bitmap createIconBitmap(Drawable icon, Context context) {
float scale = 1f;
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
icon instanceof AdaptiveIconDrawable) {
scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
}
Bitmap bitmap = createIconBitmap(icon, context, scale);
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
icon instanceof AdaptiveIconDrawable) {
bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
}
@@ -267,7 +271,7 @@ public class LauncherIcons {
final int top = (textureHeight-height) / 2;
sOldBounds.set(icon.getBounds());
if (Utilities.isAtLeastO() && icon instanceof AdaptiveIconDrawable) {
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
Math.min(left, top));
int size = Math.max(width, height);
@@ -292,7 +296,7 @@ public class LauncherIcons {
* create AdaptiveIconDrawable.
*/
static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
return drawable;
}
@@ -146,7 +146,7 @@ public class ShadowDrawable extends Drawable {
d.draw(canvas);
}
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
}
mState.mLastDrawnBitmap = bitmap;
@@ -88,7 +88,7 @@ public class CacheDataUpdatedTask extends BaseModelUpdateTask {
case OP_CACHE_UPDATE:
return true;
case OP_SESSION_UPDATE:
return si.isPromise();
return si.hasPromiseIconUi();
default:
return false;
}
@@ -431,6 +431,10 @@ public class LoaderTask implements Runnable {
}
}
if ((c.restoreFlag & ShortcutInfo.FLAG_SUPPORTS_WEB_UI) != 0) {
validTarget = false;
}
if (validTarget) {
// The shortcut points to a valid target (either no target
// or something which is ready to be used)
@@ -16,6 +16,9 @@
package com.android.launcher3.model;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
@@ -28,6 +31,7 @@ import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
import java.util.HashSet;
@@ -46,6 +50,17 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
try {
// For instant apps we do not get package-add. Use setting events to update
// any pinned icons.
ApplicationInfo ai = app.getContext()
.getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) {
app.getModel().onPackageAdded(ai.packageName, Process.myUserHandle());
}
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
// Ignore install success events as they are handled by Package add events.
return;
}
@@ -94,7 +109,7 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
if (info instanceof ShortcutInfo) {
ShortcutInfo si = (ShortcutInfo) info;
ComponentName cn = si.getTargetComponent();
if (si.isPromise() && (cn != null)
if (si.hasPromiseIconUi() && (cn != null)
&& mInstallInfo.packageName.equals(cn.getPackageName())) {
si.setInstallProgress(mInstallInfo.progress);
if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
@@ -23,6 +23,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.IconCache;
@@ -32,7 +33,6 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.ShortcutInfo;
@@ -46,6 +46,7 @@ import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -100,10 +101,11 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
appsList.addPackage(context, packages[i], mUser);
// Automatically add homescreen icon for work profile apps for below O device.
if (!Utilities.isAtLeastO() && !Process.myUserHandle().equals(mUser)) {
if (!Utilities.ATLEAST_OREO && !Process.myUserHandle().equals(mUser)) {
SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
}
}
flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
}
case OP_UPDATE:
@@ -170,12 +172,15 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
}
}
final LongArrayMap<Boolean> removedShortcuts = new LongArrayMap<>();
// Update shortcut infos
if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
final LongArrayMap<Boolean> removedShortcuts = new LongArrayMap<>();
final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
// For system apps, package manager send OP_UPDATE when an app is enabled.
final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
synchronized (dataModel) {
for (ItemInfo info : dataModel.itemsIdMap) {
if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
@@ -197,9 +202,14 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
if (cn != null && matcher.matches(si, cn)) {
AppInfo appInfo = addedOrUpdatedApps.get(cn);
// For system apps, package manager send OP_UPDATE when an
// app is enabled.
if (si.isPromise() && (mOp == OP_ADD || mOp == OP_UPDATE)) {
if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)) {
removedShortcuts.put(si.id, false);
if (mOp == OP_REMOVE) {
continue;
}
}
if (si.isPromise() && isNewApkAvailable) {
if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)) {
// Auto install icon
LauncherAppsCompat launcherApps
@@ -213,23 +223,23 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
appInfo = addedOrUpdatedApps.get(cn);
}
if ((intent == null) || (appInfo == null)) {
if (intent != null && appInfo != null) {
si.intent = intent;
si.status = ShortcutInfo.DEFAULT;
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
removedShortcuts.put(si.id, true);
continue;
}
si.intent = intent;
}
}
si.status = ShortcutInfo.DEFAULT;
infoUpdated = true;
if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
iconCache.getTitleAndIcon(si, si.usingLowResIcon);
} else {
si.status = ShortcutInfo.DEFAULT;
infoUpdated = true;
}
}
if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
&& si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
if (isNewApkAvailable &&
si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
iconCache.getTitleAndIcon(si, si.usingLowResIcon);
infoUpdated = true;
}
@@ -247,7 +257,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
if (infoUpdated) {
getModelWriter().updateItemInDatabase(si);
}
} else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
} else if (info instanceof LauncherAppWidgetInfo && isNewApkAvailable) {
LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
if (mUser.equals(widgetInfo.user)
&& widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
@@ -308,7 +318,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser));
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(removedShortcuts, true));
deleteAndBindComponentsRemoved(removeMatch);
// Remove any queued items from the install queue
@@ -335,8 +346,9 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
callbacks.notifyWidgetProvidersChanged();
}
});
} else if (Utilities.isAtLeastO() && mOp == OP_ADD) {
// Load widgets for the new package.
} else if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
// Load widgets for the new package. Changes due to app updates are handled through
// AppWidgetHost events, this is just to initialize the long-press options.
for (int i = 0; i < N; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
@@ -200,7 +200,9 @@ public class NotificationFooterLayout extends FrameLayout {
PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
Launcher.getLauncher(getContext()));
if (popup != null) {
Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight(),
final int newHeight = getResources().getDimensionPixelSize(
R.dimen.notification_empty_footer_height);
Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight() - newHeight,
getResources().getInteger(R.integer.config_removeNotificationViewDuration));
collapseFooter.addListener(new AnimatorListenerAdapter() {
@Override
@@ -208,7 +210,7 @@ public class NotificationFooterLayout extends FrameLayout {
((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
// Keep view around because gutter is aligned to it, but remove height to
// both hide the view and keep calculations correct for last dismissal.
getLayoutParams().height = 0;
getLayoutParams().height = newHeight;
requestLayout();
}
});
@@ -94,6 +94,9 @@ public class NotificationInfo implements View.OnClickListener {
@Override
public void onClick(View view) {
if (intent == null) {
return;
}
final Launcher launcher = Launcher.getLauncher(view.getContext());
Bundle activityOptions = ActivityOptions.makeClipRevealAnimation(
view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
@@ -90,9 +90,19 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
return mMainView;
}
/**
* This method is used to calculate the height to remove when dismissing the last notification.
* We subtract the height of the footer in this case since the footer should be gone or in the
* process of being removed.
* @return The height of the entire notification item, minus the footer if it still exists.
*/
public int getHeightMinusFooter() {
int footerHeight = mFooter.getParent() == null ? 0 : mFooter.getHeight();
return getHeight() - footerHeight;
if (mFooter.getParent() == null) {
return getHeight();
}
int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
R.dimen.notification_empty_footer_height);
return getHeight() - excessFooterHeight;
}
public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
@@ -32,7 +32,6 @@ import android.util.Log;
import android.util.Pair;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.SettingsObserver;
@@ -164,9 +163,6 @@ public class NotificationListener extends NotificationListenerService {
}
public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
if (!FeatureFlags.BADGE_ICONS) {
return;
}
sNotificationsChangedListener = listener;
NotificationListener notificationListener = getInstanceIfConnected();
@@ -48,7 +48,6 @@ public class PageIndicatorCaretLandscape extends PageIndicator {
setCaretDrawable(caretDrawable);
Launcher l = Launcher.getLauncher(context);
setOnTouchListener(l.getHapticFeedbackTouchListener());
setOnClickListener(l);
setOnFocusChangeListener(l.mFocusHandler);
}
@@ -141,7 +141,6 @@ public class PageIndicatorLineCaret extends PageIndicator {
super.onFinishInflate();
mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle);
mAllAppsHandle.setImageDrawable(getCaretDrawable());
mAllAppsHandle.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
mAllAppsHandle.setOnClickListener(mLauncher);
mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler);
mLauncher.setAllAppsButton(mAllAppsHandle);
@@ -275,8 +275,9 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
mNotificationItemView = (NotificationItemView) item;
boolean notificationFooterHasIcons = numNotifications > 1;
int footerHeight = notificationFooterHasIcons ?
res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
int footerHeight = res.getDimensionPixelSize(
notificationFooterHasIcons ? R.dimen.notification_footer_height
: R.dimen.notification_empty_footer_height);
item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
if (notificationFooterHasIcons) {
mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
@@ -66,7 +66,7 @@ public class DeepShortcutManager {
public static boolean supportsShortcuts(ItemInfo info) {
boolean isItemPromise = info instanceof com.android.launcher3.ShortcutInfo
&& ((com.android.launcher3.ShortcutInfo) info).isPromise();
&& ((com.android.launcher3.ShortcutInfo) info).hasPromiseIconUi();
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& !info.isDisabled() && !isItemPromise;
}
@@ -11,6 +11,7 @@ import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherCallbacks;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ComponentKeyMapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -197,7 +198,7 @@ public class LauncherExtension extends Launcher {
}
@Override
public List<ComponentKey> getPredictedApps() {
public List<ComponentKeyMapper<AppInfo>> getPredictedApps() {
// To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS
return new ArrayList<>();
}
@@ -214,10 +215,5 @@ public class LauncherExtension extends Launcher {
@Override
public void onDetachedFromWindow() {
}
@Override
public boolean shouldShowDiscoveryBounce() {
return false;
}
}
}
@@ -0,0 +1,50 @@
package com.android.launcher3.util;
/**
* Copyright (C) 2017 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.
*/
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ComponentKeyMapper<T> {
protected final ComponentKey mComponentKey;
public ComponentKeyMapper(ComponentKey key) {
this.mComponentKey = key;
}
public @Nullable T getItem(Map<ComponentKey, T> map) {
return map.get(mComponentKey);
}
public String getPackage() {
return mComponentKey.componentName.getPackageName();
}
public String getComponentClass() {
return mComponentKey.componentName.getClassName();
}
@Override
public String toString() {
return mComponentKey.toString();
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2015 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.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import java.util.Collections;
import java.util.List;
/**
* A wrapper class to access instant app related APIs.
*/
public class InstantAppResolver {
public static InstantAppResolver newInstance(Context context) {
return Utilities.getOverrideObject(
InstantAppResolver.class, context, R.string.instant_app_resolver_class);
}
public boolean isInstantApp(ApplicationInfo info) {
return false;
}
public List<ApplicationInfo> getInstantApps() {
return Collections.emptyList();
}
}
@@ -67,7 +67,7 @@ public class ManagedProfileHeuristic {
return;
}
if (Utilities.isAtLeastO() && !SessionCommitReceiver.isEnabled(context)) {
if (Utilities.ATLEAST_OREO && !SessionCommitReceiver.isEnabled(context)) {
// Just mark the folder id preference to avoid new folder creation later.
ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply();
return;
@@ -59,7 +59,7 @@ public class SystemUiController {
// Apply the state flags in priority order
int newFlags = oldFlags;
for (int stateFlag : mStates) {
if (Utilities.isAtLeastO()) {
if (Utilities.ATLEAST_OREO) {
if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
newFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else if ((stateFlag & FLAG_DARK_NAV) != 0) {
@@ -41,4 +41,8 @@ public class WidgetListRowEntry {
this.widgets = items;
}
@Override
public String toString() {
return pkgItem.packageName + ":" + widgets.size();
}
}
@@ -17,10 +17,12 @@
package com.android.launcher3.widget;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.graphics.Point;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
@@ -31,8 +33,10 @@ import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.PackageItemInfo;
@@ -74,7 +78,11 @@ public class WidgetsContainerView extends BaseContainerView
public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mAdapter = new WidgetsListAdapter(this, this, context);
LauncherAppState apps = LauncherAppState.getInstance(context);
mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context),
apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this,
new WidgetsDiffReporter(apps.getIconCache()));
mAdapter.setNotifyListener();
if (LOGD) {
Log.d(TAG, "WidgetsContainerView constructor");
}
@@ -232,7 +240,6 @@ public class WidgetsContainerView extends BaseContainerView
*/
public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
mAdapter.setWidgets(model);
mAdapter.notifyDataSetChanged();
View loader = getContentView().findViewById(R.id.loader);
if (loader != null) {
@@ -0,0 +1,141 @@
/*
* Copyright (C) 2017 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.util.Log;
import com.android.launcher3.IconCache;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly.
*/
public class WidgetsDiffReporter {
private final boolean DEBUG = false;
private final String TAG = "WidgetsDiffReporter";
private final IconCache mIconCache;
private NotifyListener mListener;
public interface NotifyListener {
void notifyDataSetChanged();
void notifyItemChanged(int index);
void notifyItemInserted(int index);
void notifyItemRemoved(int index);
}
public WidgetsDiffReporter(IconCache iconCache) {
mIconCache = iconCache;
}
public void setListener(NotifyListener listener) {
mListener = listener;
}
public void process(ArrayList<WidgetListRowEntry> currentEntries,
ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) {
if (DEBUG) {
Log.d(TAG, "process oldEntries#=" + currentEntries.size()
+ " newEntries#=" + newEntries.size());
}
if (currentEntries.size() == 0 && newEntries.size() > 0) {
currentEntries.addAll(newEntries);
mListener.notifyDataSetChanged();
return;
}
ArrayList<WidgetListRowEntry> orgEntries =
(ArrayList<WidgetListRowEntry>) currentEntries.clone();
Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator();
Iterator<WidgetListRowEntry> newIter = newEntries.iterator();
WidgetListRowEntry orgRowEntry = orgIter.next();
WidgetListRowEntry newRowEntry = newIter.next();
do {
int diff = comparePackageName(orgRowEntry, newRowEntry, comparator);
if (DEBUG) {
Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
diff, orgRowEntry != null? orgRowEntry.toString() : null,
newRowEntry != null? newRowEntry.toString() : null));
}
int index = -1;
if (diff < 0) {
index = currentEntries.indexOf(orgRowEntry);
mListener.notifyItemRemoved(index);
if (DEBUG) {
Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
orgRowEntry.titleSectionName));
}
currentEntries.remove(index);
orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
} else if (diff > 0) {
index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry):
currentEntries.size();
currentEntries.add(index, newRowEntry);
if (DEBUG) {
Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
newRowEntry.titleSectionName));
}
newRowEntry = newIter.hasNext() ? newIter.next() : null;
mListener.notifyItemInserted(index);
} else {
// same package name but,
// did the icon, title, etc, change?
// or did the widget size and desc, span, etc change?
if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) ||
!orgRowEntry.widgets.equals(newRowEntry.widgets)) {
index = currentEntries.indexOf(orgRowEntry);
currentEntries.set(index, newRowEntry);
mListener.notifyItemChanged(index);
if (DEBUG) {
Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
newRowEntry.titleSectionName));
}
}
orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
newRowEntry = newIter.hasNext() ? newIter.next() : null;
}
} while(orgRowEntry != null || newRowEntry != null);
}
/**
* Compare package name using the same comparator as in {@link WidgetsListAdapter}.
* Also handle null row pointers.
*/
private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow,
WidgetListRowEntryComparator comparator) {
if (curRow == null && newRow == null) {
throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null.");
}
if (curRow == null && newRow != null) {
return 1; // new row needs to be inserted
} else if (curRow != null && newRow == null) {
return -1; // old row needs to be deleted
}
return comparator.compare(curRow, newRow);
}
private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
return curInfo.iconBitmap.equals(newInfo.iconBitmap) &&
!mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user);
}
}
@@ -21,9 +21,10 @@ import android.support.v7.widget.RecyclerView.Adapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -55,40 +56,67 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
private final WidgetPreviewLoader mWidgetPreviewLoader;
private final LayoutInflater mLayoutInflater;
private final View.OnClickListener mIconClickListener;
private final View.OnLongClickListener mIconLongClickListener;
private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
private final AlphabeticIndexCompat mIndexer;
private final OnClickListener mIconClickListener;
private final OnLongClickListener mIconLongClickListener;
private final int mIndent;
private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
private final WidgetsDiffReporter mDiffReporter;
public WidgetsListAdapter(View.OnClickListener iconClickListener,
View.OnLongClickListener iconLongClickListener,
Context context) {
mLayoutInflater = LayoutInflater.from(context);
mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache();
mIndexer = new AlphabeticIndexCompat(context);
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat,
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
WidgetsDiffReporter diffReporter) {
mLayoutInflater = layoutInflater;
mWidgetPreviewLoader = widgetPreviewLoader;
mIndexer = indexCompat;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
mDiffReporter = diffReporter;
}
public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
mEntries.clear();
WidgetItemComparator widgetComparator = new WidgetItemComparator();
public void setNotifyListener() {
mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() {
@Override
public void notifyDataSetChanged() {
WidgetsListAdapter.this.notifyDataSetChanged();
}
@Override
public void notifyItemChanged(int index) {
WidgetsListAdapter.this.notifyItemChanged(index);
}
@Override
public void notifyItemInserted(int index) {
WidgetsListAdapter.this.notifyItemInserted(index);
}
@Override
public void notifyItemRemoved(int index) {
WidgetsListAdapter.this.notifyItemRemoved(index);
}
});
}
/**
* Update the widget list.
*/
public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>();
WidgetItemComparator widgetComparator = new WidgetItemComparator();
for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
Collections.sort(row.widgets, widgetComparator);
mEntries.add(row);
tempEntries.add(row);
}
Collections.sort(mEntries, new WidgetListRowEntryComparator());
WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
Collections.sort(tempEntries, rowComparator);
mDiffReporter.process(mEntries, tempEntries, rowComparator);
}
@Override
@@ -141,7 +141,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
Intent... commandIntents) throws Throwable {
if (!Utilities.isAtLeastO()) {
if (!Utilities.ATLEAST_OREO) {
return;
}
lockRotation(true);
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2017 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.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
import com.android.launcher3.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.MultiHashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WidgetsListAdapterTest {
private final String TAG = "WidgetsListAdapterTest";
@Mock private LayoutInflater mMockLayoutInflater;
@Mock private WidgetPreviewLoader mMockWidgetCache;
@Mock private WidgetsDiffReporter.NotifyListener mListener;
@Mock private IconCache mIconCache;
private WidgetsListAdapter mAdapter;
private AlphabeticIndexCompat mIndexCompat;
private InvariantDeviceProfile mTestProfile;
private Context mContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mIndexCompat = new AlphabeticIndexCompat(mContext);
WidgetsDiffReporter reporter = new WidgetsDiffReporter(mIconCache);
reporter.setListener(mListener);
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
mIndexCompat, null, null, reporter);
}
@Test
public void test_notifyDataSetChanged() throws Exception {
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener, times(1)).notifyDataSetChanged();
}
@Test
public void test_notifyItemInserted() throws Exception {
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(2));
verify(mListener, times(1)).notifyDataSetChanged();
verify(mListener, times(1)).notifyItemInserted(1);
}
@Test
public void test_notifyItemRemoved() throws Exception {
mAdapter.setWidgets(generateSampleMap(2));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener, times(1)).notifyDataSetChanged();
verify(mListener, times(1)).notifyItemRemoved(1);
}
@Test
public void testNotifyItemChanged_PackageIconDiff() throws Exception {
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener, times(1)).notifyDataSetChanged();
verify(mListener, times(1)).notifyItemChanged(0);
}
@Test
public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
// TODO: same package name but item number changed
}
@Test
public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
// TODO: insert and remove combined. curMap
// newMap [A, C, D] [A, B, E]
// B - C < 0, removed B from index 1 [A, E]
// E - C > 0, C inserted to index 1 [A, C, E]
// E - D > 0, D inserted to index 2 [A, C, D, E]
// E - null = -1, E deleted from index 3 [A, C, D]
}
/**
* Helper method to generate the sample widget model map that can be used for the tests
* @param num the number of WidgetItem the map should contain
* @return
*/
private MultiHashMap<PackageItemInfo, WidgetItem> generateSampleMap(int num) {
MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
if (num <= 0) return newMap;
PackageManager pm = mContext.getPackageManager();
AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
.fromProviderInfo(mContext, widgetInfo), pm, mTestProfile);
PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
pInfo.title = pInfo.packageName;
pInfo.user = wi.user;
pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
newMap.addToList(pInfo, wi);
if (newMap.size() == num) {
break;
}
}
return newMap;
}
}