am 814f1eb8: Merge "Initial changes to support predicted apps." into ub-launcher3-burnaby

* commit '814f1eb8058565f6a1299dd1cd858b163f4cbfdc':
  Initial changes to support predicted apps.
This commit is contained in:
Winson Chung
2015-05-11 22:11:52 +00:00
committed by Android Git Automerger
13 changed files with 182 additions and 73 deletions
+2 -2
View File
@@ -21,8 +21,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingTop="@dimen/apps_icon_top_bottom_padding"
android:paddingBottom="@dimen/apps_icon_top_bottom_padding"
android:focusable="true"
android:background="@drawable/focusable_view_bg"
launcher:deferShadowGeneration="true" />
-29
View File
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.BubbleTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/WorkspaceIcon.AppsCustomize"
android:id="@+id/application_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:background="@drawable/focusable_view_bg"
launcher:iconPaddingOverride="24dp"
launcher:textSizeOverride="16dp"
launcher:layoutHorizontal="true"
launcher:deferShadowGeneration="true" />
+3 -1
View File
@@ -25,7 +25,7 @@
<FrameLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_height="@dimen/apps_search_bar_height"
android:orientation="horizontal"
android:background="@drawable/apps_search_bg">
<LinearLayout
@@ -39,6 +39,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="4dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:contentDescription="@string/all_apps_button_label"
@@ -67,6 +68,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="6dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:contentDescription="@string/apps_view_search_bar_hint"
+1
View File
@@ -22,6 +22,7 @@
<dimen name="apps_grid_view_start_margin">64dp</dimen>
<dimen name="apps_view_section_text_size">26sp</dimen>
<dimen name="apps_view_row_height">76dp</dimen>
<dimen name="apps_icon_top_bottom_padding">12dp</dimen>
<!-- AppsCustomize -->
<dimen name="widget_preview_label_margin_top">8dp</dimen>
+2
View File
@@ -16,6 +16,8 @@
<resources>
<dimen name="app_icon_size">72dp</dimen>
<dimen name="apps_search_bar_height">56dp</dimen>
<dimen name="apps_icon_top_bottom_padding">16dp</dimen>
<!-- QSB -->
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
+2
View File
@@ -59,6 +59,8 @@
<dimen name="apps_view_fast_scroll_scrubber_touch_inset">-16dp</dimen>
<dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
<dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
<dimen name="apps_search_bar_height">52dp</dimen>
<dimen name="apps_icon_top_bottom_padding">8dp</dimen>
<!-- Note: This needs to match the fixed insets for the search box. -->
<dimen name="container_fixed_bounds_inset">8dp</dimen>
@@ -130,17 +130,21 @@ public class AlphabeticalAppsList {
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Whether or not this is a predicted app
public boolean isPredictedApp;
public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
AdapterItem item = new AdapterItem();
item.position = pos;
item.isSectionHeader = true;
item.sectionInfo = section;
section.sectionBreakItem = item;
return item;
}
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
int sectionAppIndex, AppInfo appInfo, int appIndex) {
int sectionAppIndex, AppInfo appInfo, int appIndex,
boolean isPredictedApp) {
AdapterItem item = new AdapterItem();
item.position = pos;
item.isSectionHeader = false;
@@ -149,6 +153,7 @@ public class AlphabeticalAppsList {
item.sectionAppIndex = sectionAppIndex;
item.appInfo = appInfo;
item.appIndex = appIndex;
item.isPredictedApp = isPredictedApp;
return item;
}
}
@@ -168,6 +173,7 @@ public class AlphabeticalAppsList {
private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
private List<SectionInfo> mSections = new ArrayList<>();
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
private List<ComponentName> mPredictedApps = new ArrayList<>();
private RecyclerView.Adapter mAdapter;
private Filter mFilter;
private AlphabeticIndexCompat mIndexer;
@@ -251,6 +257,17 @@ public class AlphabeticalAppsList {
}
}
/**
* 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.
*/
public void setPredictedApps(List<ComponentName> apps) {
mPredictedApps.clear();
mPredictedApps.addAll(apps);
onAppsUpdated();
mAdapter.notifyDataSetChanged();
}
/**
* Sets the current set of apps.
*/
@@ -336,7 +353,7 @@ public class AlphabeticalAppsList {
// Sort the list of apps
Collections.sort(mApps, mAppNameComparator.getComparator());
// Recreate the filtered and sectioned apps (for convenience for the grid layout)
// Prepare to update the list of sections, filtered apps, etc.
mFilteredApps.clear();
mSections.clear();
mSectionedFilteredApps.clear();
@@ -346,36 +363,62 @@ public class AlphabeticalAppsList {
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
int appIndex = 0;
int numApps = mApps.size();
for (AppInfo info : mApps) {
String sectionName = mIndexer.computeSectionName(info.title);
List<AppInfo> allApps = new ArrayList<>();
// Add the predicted apps to the combined list
int numPredictedApps = 0;
if (mPredictedApps != null && !mPredictedApps.isEmpty() && !hasFilter()) {
for (ComponentName cn : mPredictedApps) {
for (AppInfo info : mApps) {
if (cn.equals(info.componentName)) {
allApps.add(info);
numPredictedApps++;
break;
}
}
// Stop at the number of predicted apps
if (numPredictedApps == mNumAppsPerRow) {
break;
}
}
}
// Add all the other apps to the combined list
allApps.addAll(mApps);
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// combined list
int numApps = allApps.size();
for (int i = 0; i < numApps; i++) {
boolean isPredictedApp = i < numPredictedApps;
AppInfo info = allApps.get(i);
String sectionName = isPredictedApp ? "" : mIndexer.computeSectionName(info.title);
// Check if we want to retain this app
if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
continue;
}
// Create a new section if necessary
if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
// Create a new section if the section names do not match
if (lastSectionInfo == null ||
(!isPredictedApp && !sectionName.equals(lastSectionName))) {
lastSectionName = sectionName;
lastSectionInfo = new SectionInfo();
mSections.add(lastSectionInfo);
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
(float) appIndex / numApps);
mSections.add(lastSectionInfo);
mFastScrollerSections.add(lastFastScrollerSectionInfo);
// Create a new section item, this item is used to break the flow of items in the
// list
AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
// Create a new section item to break the flow of items in the list
if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
lastSectionInfo.sectionBreakItem = sectionItem;
AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
mSectionedFilteredApps.add(sectionItem);
}
}
// Create an app item
AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
lastSectionInfo.numApps++, info, appIndex++);
lastSectionInfo.numApps++, info, appIndex++, isPredictedApp);
if (lastSectionInfo.firstAppItem == null) {
lastSectionInfo.firstAppItem = appItem;
lastFastScrollerSectionInfo.appItem = appItem;
@@ -384,8 +427,8 @@ public class AlphabeticalAppsList {
mFilteredApps.add(info);
}
// Go through each section and try and merge some of the sections
if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
// Go through each section and try and merge some of the sections
int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f);
int sectionAppCount = 0;
for (int i = 0; i < mSections.size(); i++) {
@@ -229,7 +229,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
* Draws the fast scroller popup.
*/
private void drawFastScrollerPopup(Canvas canvas) {
if (mFastScrollAlpha > 0f) {
if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
@@ -381,16 +381,16 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
}
/**
* Returns the row index for a given position in the list.
* Returns the row index for a app index in the list.
*/
private int findRowForAppIndex(int position) {
private int findRowForAppIndex(int index) {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
int appIndex = 0;
int rowCount = 0;
for (AlphabeticalAppsList.SectionInfo info : sections) {
int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
if (appIndex + info.numApps > position) {
return rowCount + ((position - appIndex) / mNumAppsPerRow);
if (appIndex + info.numApps > index) {
return rowCount + ((index - appIndex) / mNumAppsPerRow);
}
appIndex += info.numApps;
rowCount += numRowsInSection;
@@ -15,6 +15,7 @@
*/
package com.android.launcher3;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -110,6 +111,13 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
mApps.setAdapter(mAdapter);
}
/**
* Sets the current set of predicted apps.
*/
public void setPredictedApps(List<ComponentName> apps) {
mApps.setPredictedApps(apps);
}
/**
* Sets the current set of apps.
*/
+61 -15
View File
@@ -35,13 +35,11 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public View mContent;
public boolean mIsSectionHeader;
public boolean mIsEmptyRow;
public ViewHolder(View v, boolean isSectionHeader, boolean isEmptyRow) {
public ViewHolder(View v, boolean isEmptyRow) {
super(v);
mContent = v;
mIsSectionHeader = isSectionHeader;
mIsEmptyRow = isEmptyRow;
}
}
@@ -93,13 +91,29 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
}
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
boolean hasDrawnPredictedAppDivider = false;
int childCount = parent.getChildCount();
int lastSectionTop = 0;
int lastSectionHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
if (shouldDrawItemSection(holder, child, i, items)) {
if (!isValidHolderAndChild(holder, child, items)) {
continue;
}
if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
// Draw the divider under the predicted app
DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().
getDeviceProfile();
int top = child.getTop() + child.getHeight();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int iconInset = (((right - left) / mAppsPerRow) - grid.allAppsIconSizePx) / 2;
c.drawLine(left + iconInset, top, right - iconInset, top, mPredictedAppsDividerPaint);
hasDrawnPredictedAppDivider = true;
} else if (shouldDrawItemSection(holder, i, items)) {
// At this point, we only draw sections for each section break;
int viewTopOffset = (2 * child.getPaddingTop());
int pos = holder.getPosition();
@@ -186,9 +200,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
}
/**
* Returns whether to draw the section for the given child.
* Returns whether we consider this a valid view holder for us to draw a divider or section for.
*/
private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex,
private boolean isValidHolderAndChild(ViewHolder holder, View child,
List<AlphabeticalAppsList.AdapterItem> items) {
// Ensure item is not already removed
GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
@@ -200,18 +214,44 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
if (holder == null) {
return false;
}
// Ensure we have a holder position
int pos = holder.getPosition();
if (pos < 0 || pos >= items.size()) {
return false;
}
return true;
}
/**
* Returns whether to draw the divider for a given child.
*/
private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) {
int pos = holder.getPosition();
return items.get(pos).isPredictedApp;
}
/**
* Returns whether to draw the section for the given child.
*/
private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
List<AlphabeticalAppsList.AdapterItem> items) {
int pos = holder.getPosition();
AlphabeticalAppsList.AdapterItem item = items.get(pos);
// Ensure it's not an empty row
if (holder.mIsEmptyRow) {
return false;
}
// Ensure we have a holder position
int pos = holder.getPosition();
if (pos <= 0 || pos >= items.size()) {
// Ensure this is not a section break
if (item.isSectionHeader) {
return false;
}
// Ensure this is not a predicted app
if (item.isPredictedApp) {
return false;
}
// Draw the section header for the first item in each section
return (childIndex == 0) ||
(items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader);
return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader);
}
}
@@ -232,6 +272,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
@Thunk int mStartMargin;
@Thunk int mSectionHeaderOffset;
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
@@ -254,11 +295,17 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset);
}
mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset);
mSectionTextPaint = new Paint();
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
R.dimen.apps_view_section_text_size));
mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
mSectionTextPaint.setAntiAlias(true);
mPredictedAppsDividerPaint = new Paint();
mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1.5f, res.getDisplayMetrics()));
mPredictedAppsDividerPaint.setColor(0x10000000);
mPredictedAppsDividerPaint.setAntiAlias(true);
}
/**
@@ -313,10 +360,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
switch (viewType) {
case EMPTY_VIEW_TYPE:
return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
false), false /* isSectionRow */, true /* isEmptyRow */);
false), true /* isEmptyRow */);
case SECTION_BREAK_VIEW_TYPE:
return new ViewHolder(new View(parent.getContext()), true /* isSectionRow */,
false /* isEmptyRow */);
return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */);
case ICON_VIEW_TYPE:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.apps_grid_row_icon_view, parent, false);
@@ -324,7 +370,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
icon.setOnClickListener(mIconClickListener);
icon.setOnLongClickListener(mIconLongClickListener);
icon.setFocusable(true);
return new ViewHolder(icon, false /* isSectionRow */, false /* isEmptyRow */);
return new ViewHolder(icon, false /* isEmptyRow */);
default:
throw new RuntimeException("Unexpected view type");
}
+33 -7
View File
@@ -64,6 +64,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -1018,16 +1019,22 @@ public class Launcher extends Activity
if (mOnResumeState == State.WORKSPACE) {
showWorkspace(false);
} else if (mOnResumeState == State.APPS) {
showAppsView(false /* animated */, false /* resetListToTop */);
// Don't update the predicted apps if the user is returning to launcher in the apps
// view as they may be depending on the UI to be static to switch to another app
showAppsView(false /* animated */, false /* resetListToTop */,
false /* updatePredictedApps */);
} else if (mOnResumeState == State.WIDGETS) {
showWidgetsView(false, false);
}
mOnResumeState = State.NONE;
// Restore the apps state if we are in all apps
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
// Otherwise, notify the callbacks if we are in all apps mode
if (mState == State.APPS) {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
}
}
}
@@ -2619,7 +2626,9 @@ public class Launcher extends Activity
if (isAppsViewVisible()) {
showWorkspace(true);
} else {
showAppsView(true /* animated */, false /* resetListToTop */);
// Try and refresh the set of predicted apps before we enter launcher
showAppsView(true /* animated */, false /* resetListToTop */,
true /* updatePredictedApps */);
}
}
@@ -3397,10 +3406,13 @@ public class Launcher extends Activity
/**
* Shows the apps view.
*/
void showAppsView(boolean animated, boolean resetListToTop) {
void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
if (resetListToTop) {
mAppsView.scrollToTop();
}
if (updatePredictedApps) {
tryAndUpdatePredictedApps();
}
showAppsOrWidgets(animated, State.APPS);
}
@@ -3509,12 +3521,26 @@ public class Launcher extends Activity
void exitSpringLoadedDragMode() {
if (mState == State.APPS_SPRING_LOADED) {
showAppsView(true, false);
showAppsView(true /* animated */, false /* resetListToTop */,
false /* updatePredictedApps */);
} else if (mState == State.WIDGETS_SPRING_LOADED) {
showWidgetsView(true, false);
}
}
/**
* Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
* resumed.
*/
private void tryAndUpdatePredictedApps() {
if (mLauncherCallbacks != null) {
List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
if (!apps.isEmpty()) {
mAppsView.setPredictedApps(apps);
}
}
}
void lockAllApps() {
// TODO
}
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
@@ -90,6 +91,7 @@ public interface LauncherCallbacks {
public boolean overrideWallpaperDimensions();
public boolean isLauncherPreinstalled();
public boolean overrideAllAppsSearch();
public List<ComponentName> getPredictedApps();
/**
* Returning true will immediately result in a call to {@link #setLauncherOverlayView(ViewGroup,
@@ -15,6 +15,7 @@ import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents a very trivial LauncherExtension. It primarily serves as a simple
@@ -258,6 +259,11 @@ public class LauncherExtension extends Launcher {
return false;
}
@Override
public List<ComponentName> getPredictedApps() {
return new ArrayList<>();
}
@Override
public boolean isLauncherPreinstalled() {
return false;