blob: 5f002b5790b04259c2d04cedcdb9682b75d6e02f [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.util.ScrollableLayoutManager;
import com.android.launcher3.views.ActivityContext;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The grid view adapter of all the apps.
*
* @param <T> Type of context inflating all apps.
*/
public class AllAppsGridAdapter<T extends Context & ActivityContext> extends
BaseAllAppsAdapter<T> {
public static final String TAG = "AppsGridAdapter";
private final AppsGridLayoutManager mGridLayoutMgr;
private final CopyOnWriteArrayList<OnLayoutCompletedListener> mOnLayoutCompletedListeners =
new CopyOnWriteArrayList<>();
/**
* Listener for {@link RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)}
*/
public interface OnLayoutCompletedListener {
void onLayoutCompleted();
}
/**
* Adds a {@link OnLayoutCompletedListener} to receive a callback when {@link
* RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called
*/
public void addOnLayoutCompletedListener(OnLayoutCompletedListener listener) {
mOnLayoutCompletedListeners.add(listener);
}
/**
* Removes a {@link OnLayoutCompletedListener} to not receive a callback when {@link
* RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called
*/
public void removeOnLayoutCompletedListener(OnLayoutCompletedListener listener) {
mOnLayoutCompletedListeners.remove(listener);
}
public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList apps, SearchAdapterProvider<?> adapterProvider,
PrivateSpaceHeaderViewController privateSpaceHeaderViewController) {
super(activityContext, inflater, apps, adapterProvider, privateSpaceHeaderViewController);
mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer());
setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
}
/**
* Returns the grid layout manager.
*/
public AppsGridLayoutManager getLayoutManager() {
return mGridLayoutMgr;
}
/** @return the column index that the given adapter index falls. */
public int getSpanIndex(int adapterIndex) {
AppsGridLayoutManager lm = getLayoutManager();
return lm.getSpanSizeLookup().getSpanIndex(adapterIndex, lm.getSpanCount());
}
/**
* A subclass of GridLayoutManager that overrides accessibility values during app search.
*/
public class AppsGridLayoutManager extends ScrollableLayoutManager {
public AppsGridLayoutManager(Context context) {
super(context);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
// Ensure that we only report the number apps for accessibility not including other
// adapter views
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
record.setItemCount(mApps.getNumFilteredApps());
record.setFromIndex(Math.max(0,
record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex())));
record.setToIndex(Math.max(0,
record.getToIndex() - getRowsNotForAccessibility(record.getToIndex())));
}
@Override
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
return super.getRowCountForAccessibility(recycler, state) -
getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
}
@Override
public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
ViewGroup.LayoutParams lp = host.getLayoutParams();
AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo();
if (!(lp instanceof LayoutParams) || (cic == null)) {
return;
}
LayoutParams glp = (LayoutParams) lp;
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()),
cic.getRowSpan(),
cic.getColumnIndex(),
cic.getColumnSpan(),
cic.isHeading(),
cic.isSelected()));
}
/**
* Returns the number of rows before {@param adapterPosition}, including this position
* which should not be counted towards the collection info.
*/
private int getRowsNotForAccessibility(int adapterPosition) {
List<AdapterItem> items = mApps.getAdapterItems();
adapterPosition = Math.max(adapterPosition, items.size() - 1);
int extraRows = 0;
for (int i = 0; i <= adapterPosition && i < items.size(); i++) {
if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) {
extraRows++;
}
}
return extraRows;
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
for (OnLayoutCompletedListener listener : mOnLayoutCompletedListeners) {
listener.onLayoutCompleted();
}
}
@Override
protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
// only account for the first icon in the row since they are the same size within a row
return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
? heightUntilLastPos
: (heightUntilLastPos + mCachedSizes.get(item.viewType));
}
}
@Override
public void setAppsPerRow(int appsPerRow) {
mAppsPerRow = appsPerRow;
int totalSpans = mAppsPerRow;
for (int itemPerRow : mAdapterProvider.getSupportedItemsPerRowArray()) {
if (totalSpans % itemPerRow != 0) {
totalSpans *= itemPerRow;
}
}
mGridLayoutMgr.setSpanCount(totalSpans);
}
/**
* Helper class to size the grid items.
*/
public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
public GridSpanSizer() {
super();
setSpanIndexCacheEnabled(true);
}
@Override
public int getSpanSize(int position) {
int totalSpans = mGridLayoutMgr.getSpanCount();
List<AdapterItem> items = mApps.getAdapterItems();
if (position >= items.size()) {
return totalSpans;
}
int viewType = items.get(position).viewType;
if (isIconViewType(viewType)) {
return totalSpans / mAppsPerRow;
} else {
if (mAdapterProvider.isViewSupported(viewType)) {
return totalSpans / mAdapterProvider.getItemsPerRow(viewType, mAppsPerRow);
}
// Section breaks span the full width
return totalSpans;
}
}
}
}