Merge "App discovery integration in All Apps search" into ub-launcher3-dorval
This commit is contained in:
committed by
Android (Google) Code Review
commit
86515dd148
@@ -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/badge_size"
|
||||
android:height="@dimen/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>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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="12dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="12">
|
||||
|
||||
<path
|
||||
android:fillColor="#FB8C00"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9.76511755 11.9348136 L 8.33665684 7.16088817 L 12.080006 4.41656311 L 7.49967039 4.41856896 L 6.03138903 0 L 4.57932894 4.41856896 L -1.34115008e-16 4.41656311 L 3.72612122 7.16088817 L 2.29967385 11.9348136 L 6.03138903 8.82574452 Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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.
|
||||
-->
|
||||
<com.android.launcher3.discovery.AppDiscoveryItemView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_instant_app"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:layout_alignRight="@+id/image"
|
||||
android:layout_alignBottom="@+id/image"
|
||||
android:clickable="false"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@id/image">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<com.android.launcher3.discovery.RatingView
|
||||
android:id="@+id/rating_view"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/review_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/price"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:textAllCaps="true"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
|
||||
android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
|
||||
android:src="@drawable/all_apps_divider"
|
||||
android:scaleType="fitXY"
|
||||
android:focusable="false" />
|
||||
</com.android.launcher3.discovery.AppDiscoveryItemView>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
|
||||
android:paddingRight="@dimen/container_fastscroll_thumb_max_width">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadingProgressBar"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="6dp"
|
||||
android:maxHeight="6dp"
|
||||
android:indeterminate="true"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/loadedDivider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@drawable/all_apps_divider"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -21,14 +21,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Represents an app in AllAppsView.
|
||||
*/
|
||||
@@ -44,7 +41,7 @@ public class AppInfo extends ItemInfoWithIcon {
|
||||
/**
|
||||
* {@see ShortcutInfo#isDisabled}
|
||||
*/
|
||||
int isDisabled = ShortcutInfo.DEFAULT;
|
||||
public int isDisabled = ShortcutInfo.DEFAULT;
|
||||
|
||||
public AppInfo() {
|
||||
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
|
||||
@@ -83,7 +80,6 @@ public class AppInfo extends ItemInfoWithIcon {
|
||||
title = Utilities.trim(info.title);
|
||||
intent = new Intent(info.intent);
|
||||
isDisabled = info.isDisabled;
|
||||
iconBitmap = info.iconBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
|
||||
|
||||
protected ItemInfoWithIcon() { }
|
||||
|
||||
protected ItemInfoWithIcon(ItemInfo info) {
|
||||
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
|
||||
super(info);
|
||||
iconBitmap = info.iconBitmap;
|
||||
usingLowResIcon = info.usingLowResIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,6 +1059,7 @@ public class Launcher extends BaseActivity
|
||||
if (mLauncherCallbacks != null) {
|
||||
mLauncherCallbacks.onResume();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2459,7 +2460,7 @@ public class Launcher extends BaseActivity
|
||||
throw new IllegalArgumentException("Input must have a valid intent");
|
||||
}
|
||||
boolean success = startActivitySafely(v, intent, item);
|
||||
getUserEventDispatcher().logAppLaunch(v, intent);
|
||||
getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
|
||||
|
||||
if (success && v instanceof BubbleTextView) {
|
||||
mWaitingForResume = (BubbleTextView) v;
|
||||
@@ -2708,9 +2709,10 @@ public class Launcher extends BaseActivity
|
||||
intent.setSourceBounds(getViewBounds(v));
|
||||
}
|
||||
try {
|
||||
if (Utilities.ATLEAST_MARSHMALLOW && item != null
|
||||
if (Utilities.ATLEAST_MARSHMALLOW
|
||||
&& (item instanceof ShortcutInfo)
|
||||
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
|
||||
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
|
||||
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
|
||||
&& !((ShortcutInfo) item).isPromise()) {
|
||||
// Shortcuts need some special checks due to legacy reasons.
|
||||
startShortcutIntentSafely(intent, optsBundle, item);
|
||||
|
||||
@@ -134,7 +134,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
|
||||
title = info.title;
|
||||
intent = new Intent(info.intent);
|
||||
iconResource = info.iconResource;
|
||||
iconBitmap = info.iconBitmap;
|
||||
status = info.status;
|
||||
mInstallProgress = info.mInstallProgress;
|
||||
isDisabled = info.isDisabled;
|
||||
@@ -146,8 +145,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
|
||||
title = Utilities.trim(info.title);
|
||||
intent = new Intent(info.intent);
|
||||
isDisabled = info.isDisabled;
|
||||
iconBitmap = info.iconBitmap;
|
||||
usingLowResIcon = info.usingLowResIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Selection;
|
||||
import android.text.Spannable;
|
||||
@@ -46,6 +48,8 @@ import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.discovery.AppDiscoveryItem;
|
||||
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
@@ -211,7 +215,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
|
||||
// IF scroller is at the very top OR there is no scroll bar because there is probably not
|
||||
// enough items to scroll, THEN it's okay for the container to be pulled down.
|
||||
if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
|
||||
if (mAppsRecyclerView.getCurrentScrollY() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -425,13 +429,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
@Override
|
||||
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
|
||||
if (apps != null) {
|
||||
if (mApps.setOrderedFilter(apps)) {
|
||||
mAppsRecyclerView.onSearchResultsChanged();
|
||||
}
|
||||
mApps.setOrderedFilter(apps);
|
||||
mAppsRecyclerView.onSearchResultsChanged();
|
||||
mAdapter.setLastSearchQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
|
||||
@NonNull AppDiscoveryUpdateState state) {
|
||||
if (!mLauncher.isDestroyed()) {
|
||||
mApps.onAppDiscoverySearchUpdate(app, state);
|
||||
mAppsRecyclerView.onSearchResultsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSearchResult() {
|
||||
if (mApps.setOrderedFilter(null)) {
|
||||
|
||||
@@ -33,12 +33,13 @@ import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.discovery.AppDiscoveryAppInfo;
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
|
||||
import com.android.launcher3.discovery.AppDiscoveryItemView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -67,6 +68,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
|
||||
// The divider that separates prediction icons from the app list
|
||||
public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
|
||||
public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
|
||||
public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
|
||||
|
||||
// Common view type masks
|
||||
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
|
||||
@@ -74,6 +77,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
| VIEW_TYPE_PREDICTION_DIVIDER;
|
||||
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
|
||||
| VIEW_TYPE_PREDICTION_ICON;
|
||||
public static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON
|
||||
| VIEW_TYPE_DISCOVERY_ITEM;
|
||||
|
||||
|
||||
public interface BindViewCallback {
|
||||
@@ -84,7 +89,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
* ViewHolder for each icon.
|
||||
*/
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
}
|
||||
@@ -150,7 +154,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
|
||||
int extraRows = 0;
|
||||
for (int i = 0; i <= adapterPosition; i++) {
|
||||
if ((items.get(i).viewType & VIEW_TYPE_MASK_ICON) == 0) {
|
||||
if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_CONTENT)) {
|
||||
extraRows++;
|
||||
}
|
||||
}
|
||||
@@ -273,8 +277,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case VIEW_TYPE_ICON:
|
||||
/* falls through */
|
||||
case VIEW_TYPE_PREDICTION_ICON: {
|
||||
case VIEW_TYPE_PREDICTION_ICON:
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
R.layout.all_apps_icon, parent, false);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
@@ -284,14 +287,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
icon.setOnFocusChangeListener(mIconFocusListener);
|
||||
|
||||
// Ensure the all apps icon height matches the workspace icons
|
||||
DeviceProfile profile = mLauncher.getDeviceProfile();
|
||||
Point cellSize = profile.getCellSize();
|
||||
GridLayoutManager.LayoutParams lp =
|
||||
(GridLayoutManager.LayoutParams) icon.getLayoutParams();
|
||||
lp.height = cellSize.y;
|
||||
icon.setLayoutParams(lp);
|
||||
icon.getLayoutParams().height = getCellSize().y;
|
||||
return new ViewHolder(icon);
|
||||
}
|
||||
case VIEW_TYPE_DISCOVERY_ITEM:
|
||||
AppDiscoveryItemView appDiscoveryItemView = (AppDiscoveryItemView) mLayoutInflater
|
||||
.inflate(R.layout.all_apps_discovery_item, parent, false);
|
||||
appDiscoveryItemView.init(mIconClickListener, mLauncher.getAccessibilityDelegate(),
|
||||
mIconLongClickListener);
|
||||
return new ViewHolder(appDiscoveryItemView);
|
||||
case VIEW_TYPE_EMPTY_SEARCH:
|
||||
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
|
||||
parent, false));
|
||||
@@ -308,8 +311,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
case VIEW_TYPE_SEARCH_DIVIDER:
|
||||
return new ViewHolder(mLayoutInflater.inflate(
|
||||
R.layout.all_apps_search_divider, parent, false));
|
||||
case VIEW_TYPE_APPS_LOADING_DIVIDER:
|
||||
View loadingDividerView = mLayoutInflater.inflate(
|
||||
R.layout.all_apps_discovery_loading_divider, parent, false);
|
||||
return new ViewHolder(loadingDividerView);
|
||||
case VIEW_TYPE_PREDICTION_DIVIDER:
|
||||
/* falls through */
|
||||
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
|
||||
return new ViewHolder(mLayoutInflater.inflate(
|
||||
R.layout.all_apps_divider, parent, false));
|
||||
@@ -318,23 +324,26 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
private Point getCellSize() {
|
||||
return mLauncher.getDeviceProfile().getCellSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
switch (holder.getItemViewType()) {
|
||||
case VIEW_TYPE_ICON: {
|
||||
case VIEW_TYPE_ICON:
|
||||
case VIEW_TYPE_PREDICTION_ICON:
|
||||
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
|
||||
BubbleTextView icon = (BubbleTextView) holder.itemView;
|
||||
icon.applyFromApplicationInfo(info);
|
||||
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_PREDICTION_ICON: {
|
||||
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
|
||||
BubbleTextView icon = (BubbleTextView) holder.itemView;
|
||||
icon.applyFromApplicationInfo(info);
|
||||
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
|
||||
case VIEW_TYPE_DISCOVERY_ITEM:
|
||||
AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo)
|
||||
mApps.getAdapterItems().get(position).appInfo;
|
||||
AppDiscoveryItemView view = (AppDiscoveryItemView) holder.itemView;
|
||||
view.apply(appDiscoveryAppInfo);
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_EMPTY_SEARCH:
|
||||
TextView emptyViewText = (TextView) holder.itemView;
|
||||
emptyViewText.setText(mEmptySearchMessage);
|
||||
@@ -349,6 +358,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
searchView.setVisibility(View.GONE);
|
||||
}
|
||||
break;
|
||||
case VIEW_TYPE_APPS_LOADING_DIVIDER:
|
||||
int visLoading = mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
|
||||
int visLoaded = !mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
|
||||
holder.itemView.findViewById(R.id.loadingProgressBar).setVisibility(visLoading);
|
||||
holder.itemView.findViewById(R.id.loadedDivider).setVisibility(visLoaded);
|
||||
break;
|
||||
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
|
||||
// nothing to do
|
||||
break;
|
||||
}
|
||||
if (mBindViewCallback != null) {
|
||||
mBindViewCallback.onBindView(holder);
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.graphics.DrawableFactory;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
|
||||
@@ -110,44 +111,40 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
|
||||
* all the different view types.
|
||||
*/
|
||||
public void preMeasureViews(AllAppsGridAdapter adapter) {
|
||||
View icon = adapter.onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
|
||||
final int iconHeight = icon.getLayoutParams().height;
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
|
||||
|
||||
final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
||||
getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
|
||||
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
||||
getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
|
||||
|
||||
// Icons
|
||||
BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
|
||||
AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
|
||||
int iconHeight = icon.getLayoutParams().height;
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
|
||||
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
|
||||
|
||||
// Search divider
|
||||
View searchDivider = adapter.onCreateViewHolder(this,
|
||||
AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).itemView;
|
||||
searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
|
||||
int searchDividerHeight = searchDivider.getMeasuredHeight();
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
|
||||
if (FeatureFlags.DISCOVERY_ENABLED) {
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
|
||||
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
|
||||
AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic dividers
|
||||
View divider = adapter.onCreateViewHolder(this,
|
||||
AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).itemView;
|
||||
divider.measure(widthMeasureSpec, heightMeasureSpec);
|
||||
int dividerHeight = divider.getMeasuredHeight();
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
|
||||
|
||||
// Search views
|
||||
View emptySearch = adapter.onCreateViewHolder(this,
|
||||
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).itemView;
|
||||
emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
|
||||
emptySearch.getMeasuredHeight());
|
||||
View searchMarket = adapter.onCreateViewHolder(this,
|
||||
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).itemView;
|
||||
searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
|
||||
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
|
||||
searchMarket.getMeasuredHeight());
|
||||
private void putSameHeightFor(AllAppsGridAdapter adapter, int w, int h, int... viewTypes) {
|
||||
View view = adapter.onCreateViewHolder(this, viewTypes[0]).itemView;
|
||||
view.measure(w, h);
|
||||
for (int viewType : viewTypes) {
|
||||
mViewHeights.put(viewType, view.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +204,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
|
||||
// Always scroll the view to the top so the user can see the changed results
|
||||
scrollToTop();
|
||||
|
||||
if (mApps.hasNoFilteredResults()) {
|
||||
if (mApps.shouldShowEmptySearch()) {
|
||||
if (mEmptySearchBackground == null) {
|
||||
mEmptySearchBackground = DrawableFactory.get(getContext())
|
||||
.getAllAppsBackground(getContext());
|
||||
@@ -438,4 +435,5 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
|
||||
x + mEmptySearchBackground.getIntrinsicWidth(),
|
||||
y + mEmptySearchBackground.getIntrinsicHeight());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@@ -32,6 +34,8 @@ import android.widget.TextView.OnEditorActionListener;
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.discovery.AppDiscoveryItem;
|
||||
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -46,7 +50,7 @@ public abstract class AllAppsSearchBarController
|
||||
protected AlphabeticalAppsList mApps;
|
||||
protected Callbacks mCb;
|
||||
protected ExtendedEditText mInput;
|
||||
private String mQuery;
|
||||
protected String mQuery;
|
||||
|
||||
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
|
||||
protected InputMethodManager mInputMethodManager;
|
||||
@@ -73,6 +77,14 @@ public abstract class AllAppsSearchBarController
|
||||
mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
mSearchAlgorithm = onInitializeSearch();
|
||||
|
||||
onInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* You can override this method to perform custom initialization.
|
||||
*/
|
||||
protected void onInitialized() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +129,7 @@ public abstract class AllAppsSearchBarController
|
||||
if (actionId != EditorInfo.IME_ACTION_SEARCH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if the query is empty
|
||||
String query = v.getText().toString();
|
||||
if (query.isEmpty()) {
|
||||
@@ -206,5 +219,19 @@ public abstract class AllAppsSearchBarController
|
||||
* Called when the search results should be cleared.
|
||||
*/
|
||||
void clearSearchResult();
|
||||
|
||||
|
||||
/**
|
||||
* Called when the app discovery is providing an update of search, which can either be
|
||||
* START for starting a new discovery,
|
||||
* UPDATE for providing a new search result, can be called multiple times,
|
||||
* END for indicating the end of results.
|
||||
*
|
||||
* @param app result item if UPDATE, else null
|
||||
* @param app the update state, START, UPDATE or END
|
||||
*/
|
||||
void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
|
||||
@NonNull AppDiscoveryUpdateState state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,12 +17,17 @@ package com.android.launcher3.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.compat.AlphabeticIndexCompat;
|
||||
import com.android.launcher3.config.ProviderConfig;
|
||||
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.LabelComparator;
|
||||
|
||||
@@ -48,6 +53,8 @@ public class AlphabeticalAppsList {
|
||||
|
||||
private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
|
||||
|
||||
private AppDiscoveryUpdateState mAppDiscoveryUpdateState;
|
||||
|
||||
/**
|
||||
* Info about a fast scroller section, depending if sections are merged, the fast scroller
|
||||
* sections will not be the same set as the section headers.
|
||||
@@ -106,6 +113,17 @@ public class AlphabeticalAppsList {
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo,
|
||||
int appIndex) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM;
|
||||
item.position = pos;
|
||||
item.sectionName = sectionName;
|
||||
item.appInfo = appInfo;
|
||||
item.appIndex = appIndex;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asEmptySearch(int pos) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
|
||||
@@ -134,6 +152,13 @@ public class AlphabeticalAppsList {
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asLoadingDivider(int pos) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER;
|
||||
item.position = pos;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asMarketSearch(int pos) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
|
||||
@@ -142,22 +167,24 @@ public class AlphabeticalAppsList {
|
||||
}
|
||||
}
|
||||
|
||||
private Launcher mLauncher;
|
||||
private final Launcher mLauncher;
|
||||
|
||||
// The set of apps from the system not including predictions
|
||||
private final List<AppInfo> mApps = new ArrayList<>();
|
||||
private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
|
||||
|
||||
// The set of filtered apps with the current filter
|
||||
private List<AppInfo> mFilteredApps = new ArrayList<>();
|
||||
private final List<AppInfo> mFilteredApps = new ArrayList<>();
|
||||
// The current set of adapter items
|
||||
private List<AdapterItem> mAdapterItems = new ArrayList<>();
|
||||
private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
|
||||
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
|
||||
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
|
||||
private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
|
||||
// The set of predicted app component names
|
||||
private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
|
||||
private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
|
||||
// The set of predicted apps resolved from the component names and the current set of apps
|
||||
private List<AppInfo> mPredictedApps = new ArrayList<>();
|
||||
private final List<AppInfo> mPredictedApps = new ArrayList<>();
|
||||
private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
|
||||
|
||||
// The of ordered component names as a result of a search query
|
||||
private ArrayList<ComponentKey> mSearchResults;
|
||||
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
|
||||
@@ -240,6 +267,10 @@ public class AlphabeticalAppsList {
|
||||
return (mSearchResults != null) && mFilteredApps.isEmpty();
|
||||
}
|
||||
|
||||
boolean shouldShowEmptySearch() {
|
||||
return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sorted list of filtered components.
|
||||
*/
|
||||
@@ -253,6 +284,20 @@ public class AlphabeticalAppsList {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
|
||||
@NonNull AppDiscoveryUpdateState state) {
|
||||
mAppDiscoveryUpdateState = state;
|
||||
switch (state) {
|
||||
case START:
|
||||
mDiscoveredApps.clear();
|
||||
break;
|
||||
case UPDATE:
|
||||
mDiscoveredApps.add(new AppDiscoveryAppInfo(app, mLauncher));
|
||||
break;
|
||||
}
|
||||
updateAdapterItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of predicted apps. Since this can be called before we get the full set
|
||||
* of applications, we should merge the results only in onAppsUpdated() which is idempotent.
|
||||
@@ -350,6 +395,17 @@ public class AlphabeticalAppsList {
|
||||
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
|
||||
*/
|
||||
private void updateAdapterItems() {
|
||||
refillAdapterItems();
|
||||
refreshRecyclerView();
|
||||
}
|
||||
|
||||
private void refreshRecyclerView() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void refillAdapterItems() {
|
||||
String lastSectionName = null;
|
||||
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
|
||||
int position = 0;
|
||||
@@ -435,14 +491,30 @@ public class AlphabeticalAppsList {
|
||||
mFilteredApps.add(info);
|
||||
}
|
||||
|
||||
// Append the search market item if we are currently searching
|
||||
if (hasFilter()) {
|
||||
if (hasNoFilteredResults()) {
|
||||
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
|
||||
if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
|
||||
mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
|
||||
|
||||
// Append all app discovery results
|
||||
for (int i = 0; i < mDiscoveredApps.size(); i++) {
|
||||
AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
|
||||
AdapterItem item = AdapterItem.asDiscoveryItem(position++,
|
||||
"", appDiscoveryAppInfo, appIndex++);
|
||||
mAdapterItems.add(item);
|
||||
}
|
||||
|
||||
if (!isAppDiscoveryRunning()) {
|
||||
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
|
||||
}
|
||||
} else {
|
||||
mAdapterItems.add(AdapterItem.asMarketDivider(position++));
|
||||
// Append the search market item
|
||||
if (hasNoFilteredResults()) {
|
||||
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
|
||||
} else {
|
||||
mAdapterItems.add(AdapterItem.asMarketDivider(position++));
|
||||
}
|
||||
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
|
||||
}
|
||||
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
|
||||
}
|
||||
|
||||
if (mNumAppsPerRow != 0) {
|
||||
@@ -498,11 +570,11 @@ public class AlphabeticalAppsList {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the recycler view
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
public boolean isAppDiscoveryRunning() {
|
||||
return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START
|
||||
|| mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE;
|
||||
}
|
||||
|
||||
private List<AppInfo> getFiltersAppInfos() {
|
||||
@@ -532,4 +604,5 @@ public class AlphabeticalAppsList {
|
||||
}
|
||||
return sectionName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.discovery;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
|
||||
public class AppDiscoveryAppInfo extends AppInfo {
|
||||
|
||||
private final @NonNull Launcher mLauncher;
|
||||
|
||||
public final boolean showAsDiscoveryItem;
|
||||
public final boolean isInstantApp;
|
||||
public final float rating;
|
||||
public final long reviewCount;
|
||||
public final @NonNull String publisher;
|
||||
public final @NonNull Intent installIntent;
|
||||
public final @NonNull Intent launchIntent;
|
||||
public final @Nullable String priceFormatted;
|
||||
|
||||
public AppDiscoveryAppInfo(AppDiscoveryItem item, Launcher launcher) {
|
||||
this.mLauncher = launcher;
|
||||
this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
|
||||
this.title = item.title;
|
||||
this.iconBitmap = item.bitmap;
|
||||
this.isDisabled = ShortcutInfo.DEFAULT;
|
||||
this.usingLowResIcon = false;
|
||||
this.isInstantApp = item.isInstantApp;
|
||||
this.rating = item.starRating;
|
||||
this.showAsDiscoveryItem = true;
|
||||
this.publisher = item.publisher != null ? item.publisher : "";
|
||||
this.priceFormatted = item.price;
|
||||
this.componentName = new ComponentName(item.packageName, "");
|
||||
this.installIntent = item.installIntent;
|
||||
this.launchIntent = item.launchIntent;
|
||||
this.reviewCount = item.reviewCount;
|
||||
this.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortcutInfo makeShortcut() {
|
||||
if (!isDragAndDropSupported()) {
|
||||
throw new RuntimeException("DnD is currently not supported for discovered store apps");
|
||||
}
|
||||
ShortcutInfo shortcutInfo = super.makeShortcut();
|
||||
if (isInstantApp) {
|
||||
int iconSize = iconBitmap.getWidth();
|
||||
int badgeSize = mLauncher.getResources().getDimensionPixelOffset(R.dimen.badge_size);
|
||||
Bitmap icon = Bitmap.createBitmap(iconBitmap);
|
||||
Drawable badgeDrawable = mLauncher.getDrawable(R.drawable.ic_instant_app);
|
||||
badgeDrawable.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
|
||||
Canvas canvas = new Canvas(icon);
|
||||
badgeDrawable.draw(canvas);
|
||||
shortcutInfo.iconBitmap = icon;
|
||||
}
|
||||
return shortcutInfo;
|
||||
}
|
||||
|
||||
public boolean isDragAndDropSupported() {
|
||||
return isInstantApp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.discovery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* This class represents the model for a discovered app via app discovery.
|
||||
* It holds all information for one result retrieved from an app discovery service.
|
||||
*/
|
||||
public class AppDiscoveryItem {
|
||||
|
||||
public final String packageName;
|
||||
public final boolean isInstantApp;
|
||||
public final float starRating;
|
||||
public final long reviewCount;
|
||||
public final Intent launchIntent;
|
||||
public final Intent installIntent;
|
||||
public final CharSequence title;
|
||||
public final String publisher;
|
||||
public final String price;
|
||||
public final Bitmap bitmap;
|
||||
|
||||
public AppDiscoveryItem(String packageName,
|
||||
boolean isInstantApp,
|
||||
float starRating,
|
||||
long reviewCount,
|
||||
CharSequence title,
|
||||
String publisher,
|
||||
Bitmap bitmap,
|
||||
String price,
|
||||
Intent launchIntent,
|
||||
Intent installIntent) {
|
||||
this.packageName = packageName;
|
||||
this.isInstantApp = isInstantApp;
|
||||
this.starRating = starRating;
|
||||
this.reviewCount = reviewCount;
|
||||
this.launchIntent = launchIntent;
|
||||
this.installIntent = installIntent;
|
||||
this.title = title;
|
||||
this.publisher = publisher;
|
||||
this.price = price;
|
||||
this.bitmap = bitmap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.discovery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class AppDiscoveryItemView extends RelativeLayout {
|
||||
|
||||
private static boolean SHOW_REVIEW_COUNT = false;
|
||||
|
||||
private ImageView mImage;
|
||||
private ImageView mBadge;
|
||||
private TextView mTitle;
|
||||
private TextView mRatingText;
|
||||
private RatingView mRatingView;
|
||||
private TextView mReviewCount;
|
||||
private TextView mPrice;
|
||||
private OnLongClickListener mOnLongClickListener;
|
||||
|
||||
public AppDiscoveryItemView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AppDiscoveryItemView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AppDiscoveryItemView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.mImage = (ImageView) findViewById(R.id.image);
|
||||
this.mBadge = (ImageView) findViewById(R.id.badge);
|
||||
this.mTitle = (TextView) findViewById(R.id.title);
|
||||
this.mRatingText = (TextView) findViewById(R.id.rating);
|
||||
this.mRatingView = (RatingView) findViewById(R.id.rating_view);
|
||||
this.mPrice = (TextView) findViewById(R.id.price);
|
||||
this.mReviewCount = (TextView) findViewById(R.id.review_count);
|
||||
}
|
||||
|
||||
public void init(OnClickListener clickListener,
|
||||
AccessibilityDelegate accessibilityDelegate,
|
||||
OnLongClickListener onLongClickListener) {
|
||||
setOnClickListener(clickListener);
|
||||
mImage.setOnClickListener(clickListener);
|
||||
setAccessibilityDelegate(accessibilityDelegate);
|
||||
mOnLongClickListener = onLongClickListener;
|
||||
}
|
||||
|
||||
public void apply(@NonNull AppDiscoveryAppInfo info) {
|
||||
setTag(info);
|
||||
mImage.setTag(info);
|
||||
mImage.setImageBitmap(info.iconBitmap);
|
||||
mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
|
||||
mBadge.setVisibility(info.isInstantApp ? View.VISIBLE : View.GONE);
|
||||
mTitle.setText(info.title);
|
||||
mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
|
||||
mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
|
||||
if (info.rating >= 0) {
|
||||
mRatingText.setText(new DecimalFormat("#.#").format(info.rating));
|
||||
mRatingView.setRating(info.rating);
|
||||
mRatingView.setVisibility(View.VISIBLE);
|
||||
String reviewCountFormatted = NumberFormat.getInstance().format(info.reviewCount);
|
||||
mReviewCount.setText("(" + reviewCountFormatted + ")");
|
||||
} else {
|
||||
// if we don't have a rating
|
||||
mRatingView.setVisibility(View.GONE);
|
||||
mRatingText.setText("");
|
||||
mReviewCount.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.discovery;
|
||||
|
||||
public enum AppDiscoveryUpdateState {
|
||||
START, UPDATE, END
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.discovery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.ClipDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/**
|
||||
* A simple rating view that shows stars with a rating from 0-5.
|
||||
*/
|
||||
public class RatingView extends View {
|
||||
|
||||
private static final float WIDTH_FACTOR = 0.9f;
|
||||
private static final int MAX_LEVEL = 10000;
|
||||
private static final int MAX_STARS = 5;
|
||||
|
||||
private final Drawable mStarDrawable;
|
||||
private final int mColorGray;
|
||||
private final int mColorHighlight;
|
||||
|
||||
private float rating;
|
||||
|
||||
public RatingView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RatingView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RatingView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
mStarDrawable = getResources().getDrawable(R.drawable.ic_star_rating, null);
|
||||
mColorGray = 0x1E000000;
|
||||
mColorHighlight = 0x8A000000;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = Math.min(Math.max(rating, 0), MAX_STARS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
drawStars(canvas, MAX_STARS, mColorGray);
|
||||
drawStars(canvas, rating, mColorHighlight);
|
||||
}
|
||||
|
||||
private void drawStars(Canvas canvas, float stars, int color) {
|
||||
int fullWidth = getLayoutParams().width;
|
||||
int cellWidth = fullWidth / MAX_STARS;
|
||||
int starWidth = (int) (cellWidth * WIDTH_FACTOR);
|
||||
int padding = cellWidth - starWidth;
|
||||
int fullStars = (int) stars;
|
||||
float partialStarFactor = stars - fullStars;
|
||||
|
||||
for (int i = 0; i < fullStars; i++) {
|
||||
int x = i * cellWidth + padding;
|
||||
Drawable star = mStarDrawable.getConstantState().newDrawable().mutate();
|
||||
star.setTint(color);
|
||||
star.setBounds(x, padding, x + starWidth, padding + starWidth);
|
||||
star.draw(canvas);
|
||||
}
|
||||
if (partialStarFactor > 0f) {
|
||||
int x = fullStars * cellWidth + padding;
|
||||
ClipDrawable star = new ClipDrawable(mStarDrawable,
|
||||
Gravity.LEFT, ClipDrawable.HORIZONTAL);
|
||||
star.setTint(color);
|
||||
star.setLevel((int) (MAX_LEVEL * partialStarFactor));
|
||||
star.setBounds(x, padding, x + starWidth, padding + starWidth);
|
||||
star.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,4 +49,6 @@ public final class FeatureFlags {
|
||||
public static final boolean LEGACY_ICON_TREATMENT = false;
|
||||
// When enabled, adaptive icons would have shadows baked when being stored to icon cache.
|
||||
public static final boolean ADAPTIVE_ICON_SHADOW = true;
|
||||
// When enabled, app discovery will be enabled if service is implemented
|
||||
public static final boolean DISCOVERY_ENABLED = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user