diff --git a/res/drawable/ic_instant_app.xml b/res/drawable/ic_instant_app.xml
new file mode 100644
index 0000000000..be5a3e049e
--- /dev/null
+++ b/res/drawable/ic_instant_app.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_star_rating.xml b/res/drawable/ic_star_rating.xml
new file mode 100644
index 0000000000..4e34fa33e8
--- /dev/null
+++ b/res/drawable/ic_star_rating.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/all_apps_discovery_item.xml b/res/layout/all_apps_discovery_item.xml
new file mode 100644
index 0000000000..2b21ef5707
--- /dev/null
+++ b/res/layout/all_apps_discovery_item.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/all_apps_discovery_loading_divider.xml b/res/layout/all_apps_discovery_loading_divider.xml
new file mode 100644
index 0000000000..c7b5ad23da
--- /dev/null
+++ b/res/layout/all_apps_discovery_loading_divider.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 8bf49c27e5..0ddde73c5f 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -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
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index a3d8c6a9d4..1e020e2587 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -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;
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 595e11ab9e..f9e6f4b902 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -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);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index b35dcb716d..f0bb1c0c19 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -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;
}
/**
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 0732004d44..cc5fa8ce15 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -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 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)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index f35230427c..59cac8d26a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -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 mApps = new ArrayList<>();
private final HashMap mComponentToAppMap = new HashMap<>();
// The set of filtered apps with the current filter
- private List mFilteredApps = new ArrayList<>();
+ private final List mFilteredApps = new ArrayList<>();
// The current set of adapter items
- private List mAdapterItems = new ArrayList<>();
+ private final ArrayList mAdapterItems = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
- private List mFastScrollerSections = new ArrayList<>();
+ private final List mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
- private List mPredictedAppComponents = new ArrayList<>();
+ private final List mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
- private List mPredictedApps = new ArrayList<>();
+ private final List mPredictedApps = new ArrayList<>();
+ private final List mDiscoveredApps = new ArrayList<>();
+
// The of ordered component names as a result of a search query
private ArrayList mSearchResults;
private HashMap 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 getFiltersAppInfos() {
@@ -532,4 +604,5 @@ public class AlphabeticalAppsList {
}
return sectionName;
}
+
}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
new file mode 100644
index 0000000000..50e979aac6
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -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;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
new file mode 100644
index 0000000000..7c10371d0b
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
@@ -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;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
new file mode 100644
index 0000000000..6faad87abd
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
@@ -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("");
+ }
+ }
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
new file mode 100644
index 0000000000..0700a1023f
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
@@ -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
+}
diff --git a/src/com/android/launcher3/discovery/RatingView.java b/src/com/android/launcher3/discovery/RatingView.java
new file mode 100644
index 0000000000..8fe63d6ba3
--- /dev/null
+++ b/src/com/android/launcher3/discovery/RatingView.java
@@ -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);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 5db395be6e..2bbe0a125a 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -40,7 +40,6 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 358a678412..80eebece2e 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -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;
}