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; }