Commit 28b06e6b authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

Add animation to history info header

+ Make DateDividedAdapter support multiple header items so that
  privacy disclaimers and clear browsing data button can be seperate
  items in recycler view to support show/hide animation.
+ Hide info button when info header is not visible on scrolling down
  in both history and downloads.
+ Remove header when history items are all deleted.

BUG=689322

Change-Id: Idbcae648ba277eb82b87bbe5a0667373bb09fc1a
Reviewed-on: https://chromium-review.googlesource.com/560672
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488031}
parent ec9c793c
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 The Chromium Authors. All rights reserved. <!-- Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. --> found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:paddingBottom="8dp"
android:layout_marginBottom="8dp" > android:background="@null" >
<LinearLayout
android:id="@+id/privacy_disclaimers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp"
android:visibility="gone"
android:layout_marginBottom="16dp" >
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/signed_in_not_synced"
style="@style/PrivacyDisclaimerText" />
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/signed_in_synced"
style="@style/PrivacyDisclaimerText" />
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/other_forms_of_browsing_history"
style="@style/PrivacyDisclaimerText" />
</LinearLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/list_item_single" > android:background="@drawable/list_item_single" >
<!-- TODO(twellington): change start/end padding back to 16dp and remove duplicate
<!-- TODO(twellington): change start/end padding back to 16dp FrameLayout when list item background is removed. -->
when new assets with correct built in padding
are available. -->
<Button <Button
android:id="@+id/clear_browsing_data_button" android:id="@+id/clear_browsing_data_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top"
style="@style/ButtonCompatBorderless" style="@style/ButtonCompatBorderless"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="16dp" android:paddingBottom="16dp"
...@@ -54,6 +30,5 @@ ...@@ -54,6 +30,5 @@
android:textAllCaps="true" android:textAllCaps="true"
android:textColor="@color/blue_when_enabled" android:textColor="@color/blue_when_enabled"
android:textSize="14sp" /> android:textSize="14sp" />
</FrameLayout> </FrameLayout>
</LinearLayout> </FrameLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp" >
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/signed_in_not_synced"
style="@style/PrivacyDisclaimerText" />
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/signed_in_synced"
style="@style/PrivacyDisclaimerText" />
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/other_forms_of_browsing_history"
style="@style/PrivacyDisclaimerText" />
<Space
android:id="@+id/privacy_disclaimer_bottom_space"
android:layout_width="match_parent"
android:layout_height="8dp" />
</LinearLayout>
\ No newline at end of file
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingTop="8dp"
android:clipToPadding="false"
android:visibility="gone" /> android:visibility="gone" />
<TextView <TextView
......
...@@ -9,6 +9,7 @@ import android.support.annotation.Nullable; ...@@ -9,6 +9,7 @@ import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
...@@ -167,6 +168,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter ...@@ -167,6 +168,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter
private int mFilter = DownloadFilter.FILTER_ALL; private int mFilter = DownloadFilter.FILTER_ALL;
private String mSearchQuery = EMPTY_QUERY; private String mSearchQuery = EMPTY_QUERY;
private SpaceDisplay mSpaceDisplay; private SpaceDisplay mSpaceDisplay;
private HeaderItem mSpaceDisplayHeaderItem;
private boolean mIsSearching; private boolean mIsSearching;
private boolean mShouldShowStorageInfoHeader; private boolean mShouldShowStorageInfoHeader;
...@@ -191,6 +193,8 @@ public class DownloadHistoryAdapter extends DateDividedAdapter ...@@ -191,6 +193,8 @@ public class DownloadHistoryAdapter extends DateDividedAdapter
mBackendProvider = provider; mBackendProvider = provider;
mUiConfig = uiConfig; mUiConfig = uiConfig;
generateHeaderItems();
DownloadItemSelectionDelegate selectionDelegate = DownloadItemSelectionDelegate selectionDelegate =
(DownloadItemSelectionDelegate) mBackendProvider.getSelectionDelegate(); (DownloadItemSelectionDelegate) mBackendProvider.getSelectionDelegate();
selectionDelegate.initialize(this); selectionDelegate.initialize(this);
...@@ -363,27 +367,31 @@ public class DownloadHistoryAdapter extends DateDividedAdapter ...@@ -363,27 +367,31 @@ public class DownloadHistoryAdapter extends DateDividedAdapter
holder.getItemView().displayItem(mBackendProvider, item); holder.getItemView().displayItem(mBackendProvider, item);
} }
@Override
protected void bindViewHolderForHeaderItem(ViewHolder viewHolder, HeaderItem headerItem) {
super.bindViewHolderForHeaderItem(viewHolder, headerItem);
mSpaceDisplay.onChanged();
}
@Override @Override
protected ItemGroup createGroup(long timeStamp) { protected ItemGroup createGroup(long timeStamp) {
return new DownloadItemGroup(timeStamp); return new DownloadItemGroup(timeStamp);
} }
@Override /**
protected BasicViewHolder createHeader(ViewGroup parent) { * Initialize space display view in storage info header and generate header item for it.
if (mSpaceDisplay == null) { */
mSpaceDisplay = new SpaceDisplay(parent, this); void generateHeaderItems() {
registerAdapterDataObserver(mSpaceDisplay); mSpaceDisplay = new SpaceDisplay(null, this);
if (mUiConfig != null) { View view = mSpaceDisplay.getView();
MarginResizer.createAndAttach(mSpaceDisplay.getView(), mUiConfig, registerAdapterDataObserver(mSpaceDisplay);
parent.getResources().getDimensionPixelSize( if (mUiConfig != null) {
R.dimen.list_item_default_margin), MarginResizer.createAndAttach(view, mUiConfig,
SelectableListLayout.getDefaultListItemLateralShadowSizePx( view.getResources().getDimensionPixelSize(R.dimen.list_item_default_margin),
parent.getResources())); SelectableListLayout.getDefaultListItemLateralShadowSizePx(
} view.getResources()));
} }
mSpaceDisplayHeaderItem = new HeaderItem(0, view);
mSpaceDisplay.onChanged();
return new BasicViewHolder(mSpaceDisplay.getView());
} }
/** Called when a new DownloadItem has been created by the native DownloadManager. */ /** Called when a new DownloadItem has been created by the native DownloadManager. */
...@@ -598,7 +606,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter ...@@ -598,7 +606,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter
clear(false); clear(false);
if (!filteredTimedItems.isEmpty() && !mIsSearching && mShouldShowStorageInfoHeader) { if (!filteredTimedItems.isEmpty() && !mIsSearching && mShouldShowStorageInfoHeader) {
addHeader(); setHeaders(mSpaceDisplayHeaderItem);
} }
loadItems(filteredTimedItems); loadItems(filteredTimedItems);
......
...@@ -24,12 +24,20 @@ import java.util.List; ...@@ -24,12 +24,20 @@ import java.util.List;
public class DownloadManagerToolbar extends SelectableListToolbar<DownloadHistoryItemWrapper> public class DownloadManagerToolbar extends SelectableListToolbar<DownloadHistoryItemWrapper>
implements DownloadUiObserver { implements DownloadUiObserver {
private Spinner mSpinner; private Spinner mSpinner;
private DownloadManagerUi mManager;
public DownloadManagerToolbar(Context context, AttributeSet attrs) { public DownloadManagerToolbar(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
inflateMenu(R.menu.download_manager_menu); inflateMenu(R.menu.download_manager_menu);
} }
/**
* @param manager The {@link DownloadManagerUi} associated with this toolbar.
*/
public void setManager(DownloadManagerUi manager) {
mManager = manager;
}
/** /**
* Initializes the spinner for the download filter. * Initializes the spinner for the download filter.
* @param adapter The adapter associated with the spinner. * @param adapter The adapter associated with the spinner.
...@@ -95,6 +103,12 @@ public class DownloadManagerToolbar extends SelectableListToolbar<DownloadHistor ...@@ -95,6 +103,12 @@ public class DownloadManagerToolbar extends SelectableListToolbar<DownloadHistor
mSpinner.setAdapter(null); mSpinner.setAdapter(null);
} }
@Override
protected void showNormalView() {
super.showNormalView();
mManager.updateInfoButtonVisibility();
}
@Override @Override
public void showSearchView() { public void showSearchView() {
super.showSearchView(); super.showSearchView();
......
...@@ -9,6 +9,7 @@ import android.content.ComponentName; ...@@ -9,6 +9,7 @@ import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener; import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
import android.view.LayoutInflater; import android.view.LayoutInflater;
...@@ -200,6 +201,13 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat ...@@ -200,6 +201,13 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat
// Prevent every progress update from causing a transition animation. // Prevent every progress update from causing a transition animation.
mRecyclerView.getItemAnimator().setChangeDuration(0); mRecyclerView.getItemAnimator().setChangeDuration(0);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
updateInfoButtonVisibility();
}
});
mFilterAdapter = new FilterAdapter(); mFilterAdapter = new FilterAdapter();
mFilterAdapter.initialize(this); mFilterAdapter.initialize(this);
addObserver(mFilterAdapter); addObserver(mFilterAdapter);
...@@ -207,6 +215,7 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat ...@@ -207,6 +215,7 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat
mToolbar = (DownloadManagerToolbar) mSelectableListLayout.initializeToolbar( mToolbar = (DownloadManagerToolbar) mSelectableListLayout.initializeToolbar(
R.layout.download_manager_toolbar, mBackendProvider.getSelectionDelegate(), 0, null, R.layout.download_manager_toolbar, mBackendProvider.getSelectionDelegate(), 0, null,
R.id.normal_menu_group, R.id.selection_mode_menu_group, null, this, true); R.id.normal_menu_group, R.id.selection_mode_menu_group, null, this, true);
mToolbar.setManager(this);
mToolbar.initializeFilterSpinner(mFilterAdapter); mToolbar.initializeFilterSpinner(mFilterAdapter);
mToolbar.initializeSearchView(this, R.string.download_manager_search, R.id.search_menu_id); mToolbar.initializeSearchView(this, R.string.download_manager_search, R.id.search_menu_id);
mToolbar.setInfoMenuItem(R.id.info_menu_id); mToolbar.setInfoMenuItem(R.id.info_menu_id);
...@@ -454,6 +463,23 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat ...@@ -454,6 +463,23 @@ public class DownloadManagerUi implements OnMenuItemClickListener, SearchDelegat
mSnackbarManager.dismissSnackbars(mUndoDeletionSnackbarController); mSnackbarManager.dismissSnackbars(mUndoDeletionSnackbarController);
} }
/**
* @return True if info menu item should be shown on download toolbar, false otherwise.
*/
boolean shouldShowInfoButton() {
return mHistoryAdapter.getItemCount() > 0 && !mToolbar.isSearching();
}
/**
* Update info button visibility based on whether info header is visible on download page.
*/
void updateInfoButtonVisibility() {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
boolean infoHeaderIsVisible = layoutManager.findFirstVisibleItemPosition() == 0;
mToolbar.updateInfoMenuItem(infoHeaderIsVisible && shouldShowInfoButton(),
mHistoryAdapter.shouldShowStorageInfoHeader());
}
@VisibleForTesting @VisibleForTesting
public SnackbarManager getSnackbarManagerForTesting() { public SnackbarManager getSnackbarManagerForTesting() {
return mSnackbarManager; return mSnackbarManager;
......
...@@ -15,6 +15,7 @@ import android.view.ViewGroup; ...@@ -15,6 +15,7 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.ObserverList; import org.chromium.base.ObserverList;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -114,7 +115,7 @@ public class SpaceDisplay extends RecyclerView.AdapterDataObserver { ...@@ -114,7 +115,7 @@ public class SpaceDisplay extends RecyclerView.AdapterDataObserver {
SpaceDisplay(final ViewGroup parent, DownloadHistoryAdapter historyAdapter) { SpaceDisplay(final ViewGroup parent, DownloadHistoryAdapter historyAdapter) {
mHistoryAdapter = historyAdapter; mHistoryAdapter = historyAdapter;
mView = (ViewGroup) LayoutInflater.from(parent.getContext()) mView = LayoutInflater.from(ContextUtils.getApplicationContext())
.inflate(R.layout.download_manager_ui_space_widget, parent, false); .inflate(R.layout.download_manager_ui_space_widget, parent, false);
mSpaceUsedByDownloadsTextView = (TextView) mView.findViewById(R.id.size_downloaded); mSpaceUsedByDownloadsTextView = (TextView) mView.findViewById(R.id.size_downloaded);
mSpaceUsedByOtherAppsTextView = (TextView) mView.findViewById(R.id.size_other_apps); mSpaceUsedByOtherAppsTextView = (TextView) mView.findViewById(R.id.size_other_apps);
......
...@@ -14,7 +14,7 @@ import android.view.View; ...@@ -14,7 +14,7 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout; import android.widget.Space;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -53,22 +53,24 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -53,22 +53,24 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
private final ArrayList<HistoryItemView> mItemViews; private final ArrayList<HistoryItemView> mItemViews;
private RecyclerView mRecyclerView; private RecyclerView mRecyclerView;
private ViewGroup mPrivacyDisclaimers;
private TextView mSignedInNotSyncedTextView; private TextView mSignedInNotSyncedTextView;
private TextView mSignedInSyncedTextView; private TextView mSignedInSyncedTextView;
private TextView mOtherFormsOfBrowsingHistoryTextView; private TextView mOtherFormsOfBrowsingHistoryTextView;
private Space mPrivacyDisclaimerBottomSpace;
private Button mClearBrowsingDataButton; private Button mClearBrowsingDataButton;
private FrameLayout mClearBrowsingDataButtonContainer; private HeaderItem mPrivacyDisclaimerHeaderItem;
private HeaderItem mClearBrowsingDataButtonHeaderItem;
private boolean mHasOtherFormsOfBrowsingData; private boolean mHasOtherFormsOfBrowsingData;
private boolean mHasSyncedData; private boolean mHasSyncedData;
private boolean mIsHeaderInflated;
private boolean mIsDestroyed; private boolean mIsDestroyed;
private boolean mIsInitialized; private boolean mIsInitialized;
private boolean mIsLoadingItems; private boolean mIsLoadingItems;
private boolean mIsSearching; private boolean mIsSearching;
private boolean mHasMorePotentialItems; private boolean mHasMorePotentialItems;
private boolean mClearOnNextQueryComplete; private boolean mClearOnNextQueryComplete;
private boolean mPrivacyDisclaimersVisible;
private boolean mClearBrowsingDataButtonVisible;
private long mNextQueryEndTime; private long mNextQueryEndTime;
private String mQueryText = EMPTY_QUERY; private String mQueryText = EMPTY_QUERY;
private int mDefaultTextMargin; private int mDefaultTextMargin;
...@@ -194,6 +196,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -194,6 +196,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
for (HistoryItemView itemView : mItemViews) { for (HistoryItemView itemView : mItemViews) {
itemView.onSignInStateChange(); itemView.onSignInStateChange();
} }
initialize();
updateClearBrowsingDataButtonVisibility(); updateClearBrowsingDataButtonVisibility();
setPrivacyDisclaimerVisibility(); setPrivacyDisclaimerVisibility();
} }
...@@ -252,7 +255,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -252,7 +255,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
boolean wasInitialized = mIsInitialized; boolean wasInitialized = mIsInitialized;
if (!mIsInitialized) { if (!mIsInitialized) {
if (items.size() > 0 && !mIsSearching) addHeader(); if (items.size() > 0 && !mIsSearching) setHeaders();
mIsInitialized = true; mIsInitialized = true;
} }
...@@ -298,28 +301,34 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -298,28 +301,34 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
} }
@Override @Override
protected BasicViewHolder createHeader(ViewGroup parent) { protected BasicViewHolder createFooter(ViewGroup parent) {
ViewGroup v = (ViewGroup) LayoutInflater.from(parent.getContext()).inflate( return new BasicViewHolder(
R.layout.history_header, parent, false); LayoutInflater.from(parent.getContext())
Resources resources = v.getResources(); .inflate(R.layout.indeterminate_progress_view, parent, false));
mIsHeaderInflated = true; }
mClearBrowsingDataButton = (Button) v.findViewById(R.id.clear_browsing_data_button); @Override
mClearBrowsingDataButton.setOnClickListener(new OnClickListener() { protected DateViewHolder createDateViewHolder(ViewGroup parent) {
@Override DateViewHolder viewHolder = super.createDateViewHolder(parent);
public void onClick(View v) { MarginResizer.createAndAttach(viewHolder.itemView,
mHistoryManager.openClearBrowsingDataPreference();
}
});
mClearBrowsingDataButtonContainer = (FrameLayout) mClearBrowsingDataButton.getParent();
MarginResizer.createAndAttach(mClearBrowsingDataButtonContainer,
mHistoryManager.getSelectableListLayout().getUiConfig(), mHistoryManager.getSelectableListLayout().getUiConfig(),
SelectableListLayout.getDefaultListItemLateralMarginPx(resources), 0); getDefaultTextMargin(parent.getResources()),
updateClearBrowsingDataButtonVisibility(); SelectableListLayout.getDefaultListItemLateralShadowSizePx(parent.getResources()));
return viewHolder;
mPrivacyDisclaimers = (ViewGroup) v.findViewById(R.id.privacy_disclaimers); }
mSignedInNotSyncedTextView = (TextView) v.findViewById(R.id.signed_in_not_synced); /**
* Initialize clear browsing data and privacy disclaimer header views and generate header
* items for them.
*/
void generateHeaderItems() {
ViewGroup privacyDisclaimers =
(ViewGroup) View.inflate(mHistoryManager.getSelectableListLayout().getContext(),
R.layout.history_privacy_disclaimer_header, null);
Resources resources = privacyDisclaimers.getResources();
mSignedInNotSyncedTextView =
(TextView) privacyDisclaimers.findViewById(R.id.signed_in_not_synced);
setPrivacyDisclaimerText(mSignedInNotSyncedTextView, setPrivacyDisclaimerText(mSignedInNotSyncedTextView,
R.string.android_history_no_synced_results, LEARN_MORE_LINK); R.string.android_history_no_synced_results, LEARN_MORE_LINK);
MarginResizer.createAndAttach(mSignedInNotSyncedTextView, MarginResizer.createAndAttach(mSignedInNotSyncedTextView,
...@@ -327,7 +336,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -327,7 +336,7 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
getDefaultTextMargin(resources), getDefaultTextMargin(resources),
SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources)); SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources));
mSignedInSyncedTextView = (TextView) v.findViewById(R.id.signed_in_synced); mSignedInSyncedTextView = (TextView) privacyDisclaimers.findViewById(R.id.signed_in_synced);
setPrivacyDisclaimerText(mSignedInSyncedTextView, setPrivacyDisclaimerText(mSignedInSyncedTextView,
R.string.android_history_has_synced_results, LEARN_MORE_LINK); R.string.android_history_has_synced_results, LEARN_MORE_LINK);
MarginResizer.createAndAttach(mSignedInSyncedTextView, MarginResizer.createAndAttach(mSignedInSyncedTextView,
...@@ -335,8 +344,8 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -335,8 +344,8 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
getDefaultTextMargin(resources), getDefaultTextMargin(resources),
SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources)); SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources));
mOtherFormsOfBrowsingHistoryTextView = (TextView) v.findViewById( mOtherFormsOfBrowsingHistoryTextView =
R.id.other_forms_of_browsing_history); (TextView) privacyDisclaimers.findViewById(R.id.other_forms_of_browsing_history);
boolean flagEnabled = ChromeFeatureList.isEnabled(ChromeFeatureList.TABS_IN_CBD); boolean flagEnabled = ChromeFeatureList.isEnabled(ChromeFeatureList.TABS_IN_CBD);
int disclaimerTextId = flagEnabled ? R.string.android_history_other_forms_of_history_new int disclaimerTextId = flagEnabled ? R.string.android_history_other_forms_of_history_new
: R.string.android_history_other_forms_of_history; : R.string.android_history_other_forms_of_history;
...@@ -348,25 +357,39 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -348,25 +357,39 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
getDefaultTextMargin(resources), getDefaultTextMargin(resources),
SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources)); SelectableListLayout.getDefaultListItemLateralShadowSizePx(resources));
setPrivacyDisclaimerVisibility(); mPrivacyDisclaimerBottomSpace =
(Space) privacyDisclaimers.findViewById(R.id.privacy_disclaimer_bottom_space);
return new BasicViewHolder(v); ViewGroup clearBrowsingDataButtonContainer =
} (ViewGroup) View.inflate(mHistoryManager.getSelectableListLayout().getContext(),
R.layout.history_clear_browsing_data_header, null);
@Override mClearBrowsingDataButton = (Button) clearBrowsingDataButtonContainer.findViewById(
protected BasicViewHolder createFooter(ViewGroup parent) { R.id.clear_browsing_data_button);
return new BasicViewHolder(LayoutInflater.from(parent.getContext()).inflate( mClearBrowsingDataButton.setOnClickListener(new OnClickListener() {
R.layout.indeterminate_progress_view, parent, false)); @Override
public void onClick(View v) {
mHistoryManager.openClearBrowsingDataPreference();
}
});
MarginResizer.createAndAttach(clearBrowsingDataButtonContainer,
mHistoryManager.getSelectableListLayout().getUiConfig(),
SelectableListLayout.getDefaultListItemLateralMarginPx(resources), 0);
mPrivacyDisclaimerHeaderItem = new HeaderItem(0, privacyDisclaimers);
mClearBrowsingDataButtonHeaderItem = new HeaderItem(1, clearBrowsingDataButtonContainer);
updateClearBrowsingDataButtonVisibility();
setPrivacyDisclaimerVisibility();
} }
@Override /**
protected DateViewHolder createDateViewHolder(ViewGroup parent) { * Pass header items to {@link #setHeaders(HeaderItem...)} as parameters.
DateViewHolder viewHolder = super.createDateViewHolder(parent); */
MarginResizer.createAndAttach(viewHolder.itemView, private void setHeaders() {
mHistoryManager.getSelectableListLayout().getUiConfig(), ArrayList<HeaderItem> args = new ArrayList<>();
getDefaultTextMargin(parent.getResources()), if (mPrivacyDisclaimersVisible) args.add(mPrivacyDisclaimerHeaderItem);
SelectableListLayout.getDefaultListItemLateralShadowSizePx(parent.getResources())); if (mClearBrowsingDataButtonVisible) args.add(mClearBrowsingDataButtonHeaderItem);
return viewHolder; setHeaders(args.toArray(new HeaderItem[args.size()]));
} }
private void setPrivacyDisclaimerText(TextView view, int stringId, final String url) { private void setPrivacyDisclaimerText(TextView view, int stringId, final String url) {
...@@ -396,26 +419,29 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -396,26 +419,29 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
* Set visibility for privacy disclaimer layout and views. * Set visibility for privacy disclaimer layout and views.
*/ */
void setPrivacyDisclaimerVisibility() { void setPrivacyDisclaimerVisibility() {
if (!mIsHeaderInflated) return;
boolean show = hasPrivacyDisclaimers() && mHistoryManager.shouldShowInfoHeaderIfAvailable();
boolean isSignedIn = ChromeSigninController.get().isSignedIn(); boolean isSignedIn = ChromeSigninController.get().isSignedIn();
boolean shouldShowPrivacyDisclaimers =
hasPrivacyDisclaimers() && mHistoryManager.shouldShowInfoHeaderIfAvailable();
mSignedInNotSyncedTextView.setVisibility( mSignedInNotSyncedTextView.setVisibility(
!mHasSyncedData && isSignedIn ? View.VISIBLE : View.GONE); !mHasSyncedData && isSignedIn ? View.VISIBLE : View.GONE);
mSignedInSyncedTextView.setVisibility(mHasSyncedData ? View.VISIBLE : View.GONE); mSignedInSyncedTextView.setVisibility(mHasSyncedData ? View.VISIBLE : View.GONE);
mOtherFormsOfBrowsingHistoryTextView.setVisibility( mOtherFormsOfBrowsingHistoryTextView.setVisibility(
mHasOtherFormsOfBrowsingData ? View.VISIBLE : View.GONE); mHasOtherFormsOfBrowsingData ? View.VISIBLE : View.GONE);
mPrivacyDisclaimers.setVisibility(show ? View.VISIBLE : View.GONE); // Prevent from refreshing the recycler view if header visibility is not changed.
if (mPrivacyDisclaimersVisible == shouldShowPrivacyDisclaimers) return;
mPrivacyDisclaimersVisible = shouldShowPrivacyDisclaimers;
if (mIsInitialized) setHeaders();
} }
private void updateClearBrowsingDataButtonVisibility() { private void updateClearBrowsingDataButtonVisibility() {
// If the history header is not showing (e.g. when there is no browsing history), // If the history header is not showing (e.g. when there is no browsing history),
// mClearBrowsingDataButton will be null. // mClearBrowsingDataButton will be null.
if (mClearBrowsingDataButton == null) return; if (mClearBrowsingDataButton == null) return;
boolean shouldShowButton = PrefServiceBridge.getInstance().canDeleteBrowsingHistory();
mClearBrowsingDataButtonContainer.setVisibility( if (mClearBrowsingDataButtonVisible == shouldShowButton) return;
!PrefServiceBridge.getInstance().canDeleteBrowsingHistory() ? View.GONE : mClearBrowsingDataButtonVisible = shouldShowButton;
View.VISIBLE); mPrivacyDisclaimerBottomSpace.setVisibility(shouldShowButton ? View.GONE : View.VISIBLE);
if (mIsInitialized) setHeaders();
} }
private int getDefaultTextMargin(Resources resources) { private int getDefaultTextMargin(Resources resources) {
...@@ -441,12 +467,27 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor ...@@ -441,12 +467,27 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
} }
@VisibleForTesting @VisibleForTesting
ViewGroup getPrivacyDisclaimersForTests() { ItemGroup getFirstGroupForTests() {
return mPrivacyDisclaimers; return getGroupAt(0).first;
}
@VisibleForTesting
void setClearBrowsingDataButtonVisibilityForTest(boolean isVisible) {
if (mClearBrowsingDataButton == null) return;
if (mClearBrowsingDataButtonVisible == isVisible) return;
mClearBrowsingDataButtonVisible = isVisible;
if (mIsInitialized) setHeaders();
} }
@VisibleForTesting @VisibleForTesting
public ArrayList<HistoryItemView> getItemViewsForTests() { public ArrayList<HistoryItemView> getItemViewsForTests() {
return mItemViews; return mItemViews;
} }
@VisibleForTesting
void generateHeaderItemsForTest() {
mPrivacyDisclaimerHeaderItem = new HeaderItem(0, null);
mClearBrowsingDataButtonHeaderItem = new HeaderItem(1, null);
mClearBrowsingDataButtonVisible = true;
}
} }
...@@ -134,17 +134,23 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser ...@@ -134,17 +134,23 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser
mLargeIconBridge.createCache(maxSize); mLargeIconBridge.createCache(maxSize);
// 7. Initialize the adapter to load items. // 7. Initialize the adapter to load items.
mHistoryAdapter.generateHeaderItems();
mHistoryAdapter.initialize(); mHistoryAdapter.initialize();
// 8. Add scroll listener to page in more items when necessary. // 8. Add scroll listener to show/hide info button on scroll and page in more items
// when necessary.
mRecyclerView.addOnScrollListener(new OnScrollListener() { mRecyclerView.addOnScrollListener(new OnScrollListener() {
@Override @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!mHistoryAdapter.canLoadMoreItems()) return;
// Load more items if the scroll position is close to the bottom of the list.
LinearLayoutManager layoutManager = LinearLayoutManager layoutManager =
(LinearLayoutManager) recyclerView.getLayoutManager(); (LinearLayoutManager) recyclerView.getLayoutManager();
// Show info button if available if first visible position is close to info header;
// otherwise hide info button.
mToolbar.updateInfoMenuItem(infoHeaderIsVisible() && shouldShowInfoButton(),
shouldShowInfoHeaderIfAvailable());
if (!mHistoryAdapter.canLoadMoreItems()) return;
// Load more items if the scroll position is close to the bottom of the list.
if (layoutManager.findLastVisibleItemPosition() if (layoutManager.findLastVisibleItemPosition()
> (mHistoryAdapter.getItemCount() - 25)) { > (mHistoryAdapter.getItemCount() - 25)) {
mHistoryAdapter.loadMoreItems(); mHistoryAdapter.loadMoreItems();
...@@ -414,7 +420,8 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser ...@@ -414,7 +420,8 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser
* @return True if info menu item should be shown on history toolbar, false otherwise. * @return True if info menu item should be shown on history toolbar, false otherwise.
*/ */
boolean shouldShowInfoButton() { boolean shouldShowInfoButton() {
return mHistoryAdapter.hasPrivacyDisclaimers(); return mHistoryAdapter.hasPrivacyDisclaimers() && mHistoryAdapter.getItemCount() > 0
&& !mToolbar.isSearching();
} }
/** /**
...@@ -425,6 +432,14 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser ...@@ -425,6 +432,14 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser
return mShouldShowInfoHeader; return mShouldShowInfoHeader;
} }
/**
* @return True if info header is visible on history page.
*/
boolean infoHeaderIsVisible() {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
return (layoutManager.findFirstVisibleItemPosition() == 0);
}
@Override @Override
public void onSignedIn() { public void onSignedIn() {
mToolbar.onSignInStateChange(); mToolbar.onSignInStateChange();
......
...@@ -44,8 +44,8 @@ public class HistoryManagerToolbar extends SelectableListToolbar<HistoryItem> { ...@@ -44,8 +44,8 @@ public class HistoryManagerToolbar extends SelectableListToolbar<HistoryItem> {
@Override @Override
protected void showNormalView() { protected void showNormalView() {
super.showNormalView(); super.showNormalView();
updateInfoMenuItem( updateInfoMenuItem(mManager.infoHeaderIsVisible() && mManager.shouldShowInfoButton(),
mManager.shouldShowInfoButton(), mManager.shouldShowInfoHeaderIfAvailable()); mManager.shouldShowInfoHeaderIfAvailable());
} }
@Override @Override
...@@ -77,9 +77,7 @@ public class HistoryManagerToolbar extends SelectableListToolbar<HistoryItem> { ...@@ -77,9 +77,7 @@ public class HistoryManagerToolbar extends SelectableListToolbar<HistoryItem> {
@Override @Override
protected void onDataChanged(int numItems) { protected void onDataChanged(int numItems) {
super.onDataChanged(numItems); super.onDataChanged(numItems);
getMenu() getMenu().findItem(R.id.info_menu_id).setVisible(mManager.shouldShowInfoButton());
.findItem(R.id.info_menu_id)
.setVisible(mManager.shouldShowInfoButton() && !mIsSearching && numItems > 0);
} }
/** /**
......
...@@ -105,6 +105,41 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -105,6 +105,41 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
public abstract long getStableId(); public abstract long getStableId();
} }
/**
* Contains information of a single header that this adapter uses to manage headers.
*/
public static class HeaderItem extends TimedItem {
private final long mStableId;
private final View mView;
/**
* Initialize stable id and view associated with this HeaderItem.
* @param position Position of this HeaderItem in the header group.
* @param view View associated with this HeaderItem.
*/
public HeaderItem(int position, View view) {
mStableId = getTimestamp() - position;
mView = view;
}
@Override
public long getTimestamp() {
return Long.MAX_VALUE;
}
@Override
public long getStableId() {
return mStableId;
}
/**
* @return The View associated with this HeaderItem.
*/
public View getView() {
return mView;
}
}
/** /**
* A {@link RecyclerView.ViewHolder} that displays a date header. * A {@link RecyclerView.ViewHolder} that displays a date header.
*/ */
...@@ -193,6 +228,10 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -193,6 +228,10 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
mItems.remove(item); mItems.remove(item);
} }
public void removeAllItems() {
mItems.clear();
}
/** Records the position of all the TimedItems in this group, relative to the full list. */ /** Records the position of all the TimedItems in this group, relative to the full list. */
public void setPosition(int index) { public void setPosition(int index) {
assert mIndex == 0 || mIndex == TimedItem.INVALID_POSITION; assert mIndex == 0 || mIndex == TimedItem.INVALID_POSITION;
...@@ -225,15 +264,18 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -225,15 +264,18 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
* @return The size of this group. * @return The size of this group.
*/ */
public int size() { public int size() {
if (mIsListHeader || mIsListFooter) return 1; if (mIsListHeader) return mItems.size();
if (mIsListFooter) return 1;
// Plus 1 to account for the date header. // Plus 1 to account for the date header.
return mItems.size() + 1; return mItems.size() + 1;
} }
public TimedItem getItemAt(int index) { public TimedItem getItemAt(int index) {
// 0 is allocated to the date header. The list header has no items. // 0 is allocated to the date header. Return item if list header has
if (index <= 0 || mIsListHeader || mIsListFooter) return null; // header items, otherwise return null.
if (mIsListHeader) return mItems.get(index);
if (index <= 0 || mIsListFooter) return null;
sortIfNeeded(); sortIfNeeded();
return mItems.get(index - 1); return mItems.get(index - 1);
...@@ -305,12 +347,15 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -305,12 +347,15 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
protected abstract ViewHolder createViewHolder(ViewGroup parent); protected abstract ViewHolder createViewHolder(ViewGroup parent);
/** /**
* Creates a {@link BasicViewHolder} in the given view parent for the header. * Creates a {@link BasicViewHolder} in the given view parent for the header. The default
* implementation will create an empty FrameLayout container as the view holder.
* @see #onCreateViewHolder(ViewGroup, int) * @see #onCreateViewHolder(ViewGroup, int)
*/ */
@Nullable
protected BasicViewHolder createHeader(ViewGroup parent) { protected BasicViewHolder createHeader(ViewGroup parent) {
return null; // Create an empty layout as a container for the header view.
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.date_divided_adapter_header_view_holder, parent, false);
return new BasicViewHolder(v);
} }
/** /**
...@@ -362,6 +407,18 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -362,6 +407,18 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
protected void bindViewHolderForSubsectionHeader( protected void bindViewHolderForSubsectionHeader(
SubsectionHeaderViewHolder holder, TimedItem timedItem) {} SubsectionHeaderViewHolder holder, TimedItem timedItem) {}
/**
* Binds the {@link BasicViewHolder} with the given {@link HeaderItem}.
* @see #onBindViewHolder(ViewHolder, int)
*/
protected void bindViewHolderForHeaderItem(ViewHolder viewHolder, HeaderItem headerItem) {
BasicViewHolder basicViewHolder = (BasicViewHolder) viewHolder;
View v = headerItem.getView();
((ViewGroup) basicViewHolder.itemView).removeAllViews();
if (v.getParent() != null) ((ViewGroup) v.getParent()).removeView(v);
((ViewGroup) basicViewHolder.itemView).addView(v);
}
/** /**
* Gets the resource id of the view showing the date header. * Gets the resource id of the view showing the date header.
* Contract for subclasses: this view should be a {@link TextView}. * Contract for subclasses: this view should be a {@link TextView}.
...@@ -420,17 +477,40 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -420,17 +477,40 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
} }
/** /**
* Adds a header as the first group in this adapter. * Add a list of headers as the first group in this adapter. If headerItems has no items,
* the header group will not be created. Otherwise, header items will be added as child items
* to the header group. Note that any previously added header items will be removed.
* {@link #bindViewHolderForHeaderItem(ViewHolder, HeaderItem)} will bind the HeaderItem views
* to the given ViewHolder. Sub-classes may override #bindViewHolderForHeaderItem and
* (@link #createHeader(ViewGroup)} if custom behavior is needed.
*
* @param headerItems Zero or more header items to be add to the header item group.
*/ */
public void addHeader() { public void setHeaders(HeaderItem... headerItems) {
assert mSize == 0; if (headerItems == null || headerItems.length == 0) {
removeHeader();
return;
}
ItemGroup header = new ItemGroup(Long.MAX_VALUE); ItemGroup header;
header.mIsListHeader = true; if (mHasListHeader) {
header = mGroups.first();
mSize -= header.size();
header.removeAllItems();
} else {
header = new ItemGroup(Long.MAX_VALUE);
header.mIsListHeader = true;
mGroups.add(header);
}
mGroups.add(header); for (HeaderItem item : headerItems) {
mSize++; header.addItem(item);
mSize++;
}
mHasListHeader = true; mHasListHeader = true;
setGroupPositions();
notifyDataSetChanged();
} }
/** /**
...@@ -439,8 +519,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -439,8 +519,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
public void removeHeader() { public void removeHeader() {
if (!mHasListHeader) return; if (!mHasListHeader) return;
mSize -= mGroups.first().size();
mGroups.remove(mGroups.first()); mGroups.remove(mGroups.first());
mSize--;
mHasListHeader = false; mHasListHeader = false;
setGroupPositions(); setGroupPositions();
...@@ -510,7 +590,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -510,7 +590,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
public Pair<Date, TimedItem> getItemAt(int position) { public Pair<Date, TimedItem> getItemAt(int position) {
Pair<ItemGroup, Integer> pair = getGroupAt(position); Pair<ItemGroup, Integer> pair = getGroupAt(position);
ItemGroup group = pair.first; ItemGroup group = pair.first;
return new Pair<>(group.mDate, group.getItemAt(pair.second)); return new Pair<>(group.mDate,
group.mIsListHeader ? group.getItemAt(position) : group.getItemAt(pair.second));
} }
@Override @Override
...@@ -556,6 +637,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -556,6 +637,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
bindViewHolderForSubsectionHeader((SubsectionHeaderViewHolder) holder, pair.second); bindViewHolderForSubsectionHeader((SubsectionHeaderViewHolder) holder, pair.second);
} else if (!(holder instanceof BasicViewHolder)) { } else if (!(holder instanceof BasicViewHolder)) {
bindViewHolderForTimedItem(holder, pair.second); bindViewHolderForTimedItem(holder, pair.second);
} else if (pair.second instanceof HeaderItem) {
bindViewHolderForHeaderItem(holder, (HeaderItem) pair.second);
} }
} }
...@@ -569,7 +652,7 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder ...@@ -569,7 +652,7 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
*/ */
protected Pair<ItemGroup, Integer> getGroupAt(int position) { protected Pair<ItemGroup, Integer> getGroupAt(int position) {
// TODO(ianwen): Optimize the performance if the number of groups becomes too large. // TODO(ianwen): Optimize the performance if the number of groups becomes too large.
if (mHasListHeader && position == 0) { if (mHasListHeader && position < mGroups.first().size()) {
assert mGroups.first().mIsListHeader; assert mGroups.first().mIsListHeader;
return new Pair<>(mGroups.first(), TYPE_HEADER); return new Pair<>(mGroups.first(), TYPE_HEADER);
} }
......
...@@ -90,7 +90,7 @@ public class DownloadHistoryAdapterTest { ...@@ -90,7 +90,7 @@ public class DownloadHistoryAdapterTest {
editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, true).apply(); editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, true).apply();
} }
private void initializeAdapter(boolean showOffTheRecord) throws Exception { private void initializeAdapter(boolean showOffTheRecord, boolean hasHeader) throws Exception {
mObserver = new Observer(); mObserver = new Observer();
mAdapter = new DownloadHistoryAdapter(showOffTheRecord, null); mAdapter = new DownloadHistoryAdapter(showOffTheRecord, null);
mAdapter.registerAdapterDataObserver(mObserver); mAdapter.registerAdapterDataObserver(mObserver);
...@@ -106,14 +106,17 @@ public class DownloadHistoryAdapterTest { ...@@ -106,14 +106,17 @@ public class DownloadHistoryAdapterTest {
} }
}); });
mDownloadDelegate.addCallback.waitForCallback(0); mDownloadDelegate.addCallback.waitForCallback(0);
mObserver.onChangedCallback.waitForCallback(callCount, 1); // If header should be added, onChanged() will be called twice because both setHeaders()
// and loadMoreItems() will call notifyDataSetChanged(). Otherwise, setHeaders() will not
// be called and onChanged() will only be called once.
mObserver.onChangedCallback.waitForCallback(callCount, hasHeader ? 2 : 1);
} }
/** Nothing downloaded, nothing shown. */ /** Nothing downloaded, nothing shown. */
@Test @Test
@SmallTest @SmallTest
public void testInitialize_Empty() throws Exception { public void testInitialize_Empty() throws Exception {
initializeAdapter(false); initializeAdapter(false, false);
Assert.assertEquals(0, mAdapter.getItemCount()); Assert.assertEquals(0, mAdapter.getItemCount());
Assert.assertEquals(0, mAdapter.getTotalDownloadSize()); Assert.assertEquals(0, mAdapter.getTotalDownloadSize());
...@@ -133,7 +136,7 @@ public class DownloadHistoryAdapterTest { ...@@ -133,7 +136,7 @@ public class DownloadHistoryAdapterTest {
public void testInitialize_SingleItem() throws Exception { public void testInitialize_SingleItem() throws Exception {
DownloadItem item = StubbedProvider.createDownloadItem(0, "19840116 12:00"); DownloadItem item = StubbedProvider.createDownloadItem(0, "19840116 12:00");
mDownloadDelegate.regularItems.add(item); mDownloadDelegate.regularItems.add(item);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item); checkAdapterContents(HEADER, null, item);
Assert.assertEquals(1, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
} }
...@@ -146,7 +149,7 @@ public class DownloadHistoryAdapterTest { ...@@ -146,7 +149,7 @@ public class DownloadHistoryAdapterTest {
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01");
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.regularItems.add(item1); mDownloadDelegate.regularItems.add(item1);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item1, item0); checkAdapterContents(HEADER, null, item1, item0);
Assert.assertEquals(11, mAdapter.getTotalDownloadSize()); Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
} }
...@@ -159,7 +162,7 @@ public class DownloadHistoryAdapterTest { ...@@ -159,7 +162,7 @@ public class DownloadHistoryAdapterTest {
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840117 12:00"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840117 12:00");
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.regularItems.add(item1); mDownloadDelegate.regularItems.add(item1);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item1, null, item0); checkAdapterContents(HEADER, null, item1, null, item0);
Assert.assertEquals(11, mAdapter.getTotalDownloadSize()); Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
} }
...@@ -172,7 +175,7 @@ public class DownloadHistoryAdapterTest { ...@@ -172,7 +175,7 @@ public class DownloadHistoryAdapterTest {
editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, false).apply(); editor.putBoolean(PREF_SHOW_STORAGE_INFO_HEADER, false).apply();
DownloadItem item = StubbedProvider.createDownloadItem(0, "19840116 12:00"); DownloadItem item = StubbedProvider.createDownloadItem(0, "19840116 12:00");
mDownloadDelegate.regularItems.add(item); mDownloadDelegate.regularItems.add(item);
initializeAdapter(false); initializeAdapter(false, false);
checkAdapterContents(null, item); checkAdapterContents(null, item);
Assert.assertEquals(1, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
} }
...@@ -185,7 +188,7 @@ public class DownloadHistoryAdapterTest { ...@@ -185,7 +188,7 @@ public class DownloadHistoryAdapterTest {
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01");
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.regularItems.add(item1); mDownloadDelegate.regularItems.add(item1);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item1, item0); checkAdapterContents(HEADER, null, item1, item0);
Assert.assertEquals(11, mAdapter.getTotalDownloadSize()); Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
...@@ -216,7 +219,7 @@ public class DownloadHistoryAdapterTest { ...@@ -216,7 +219,7 @@ public class DownloadHistoryAdapterTest {
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:01");
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.offTheRecordItems.add(item1); mDownloadDelegate.offTheRecordItems.add(item1);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item0); checkAdapterContents(HEADER, null, item0);
Assert.assertEquals(1, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
} }
...@@ -229,7 +232,7 @@ public class DownloadHistoryAdapterTest { ...@@ -229,7 +232,7 @@ public class DownloadHistoryAdapterTest {
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:00"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840116 12:00");
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.offTheRecordItems.add(item1); mDownloadDelegate.offTheRecordItems.add(item1);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents(HEADER, null, item0, item1); checkAdapterContents(HEADER, null, item0, item1);
Assert.assertEquals(11, mAdapter.getTotalDownloadSize()); Assert.assertEquals(11, mAdapter.getTotalDownloadSize());
} }
...@@ -244,7 +247,7 @@ public class DownloadHistoryAdapterTest { ...@@ -244,7 +247,7 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.regularItems.add(item0); mDownloadDelegate.regularItems.add(item0);
mDownloadDelegate.offTheRecordItems.add(item1); mDownloadDelegate.offTheRecordItems.add(item1);
mOfflineDelegate.items.add(item2); mOfflineDelegate.items.add(item2);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents(HEADER, null, item2, null, item0, item1); checkAdapterContents(HEADER, null, item2, null, item0, item1);
Assert.assertEquals(100011, mAdapter.getTotalDownloadSize()); Assert.assertEquals(100011, mAdapter.getTotalDownloadSize());
} }
...@@ -254,7 +257,7 @@ public class DownloadHistoryAdapterTest { ...@@ -254,7 +257,7 @@ public class DownloadHistoryAdapterTest {
@SmallTest @SmallTest
public void testUpdate_UpdateItems() throws Exception { public void testUpdate_UpdateItems() throws Exception {
// Start with an empty Adapter. // Start with an empty Adapter.
initializeAdapter(false); initializeAdapter(false, false);
Assert.assertEquals(0, mAdapter.getItemCount()); Assert.assertEquals(0, mAdapter.getItemCount());
Assert.assertEquals(0, mAdapter.getTotalDownloadSize()); Assert.assertEquals(0, mAdapter.getTotalDownloadSize());
...@@ -267,7 +270,7 @@ public class DownloadHistoryAdapterTest { ...@@ -267,7 +270,7 @@ public class DownloadHistoryAdapterTest {
Assert.assertEquals(1, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
// Add a second item with a different date. // Add a second item with a different date.
Assert.assertEquals(2, mObserver.onChangedCallback.getCallCount()); Assert.assertEquals(3, mObserver.onChangedCallback.getCallCount());
DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840117 12:00"); DownloadItem item1 = StubbedProvider.createDownloadItem(1, "19840117 12:00");
mAdapter.onDownloadItemCreated(item1); mAdapter.onDownloadItemCreated(item1);
mObserver.onChangedCallback.waitForCallback(2); mObserver.onChangedCallback.waitForCallback(2);
...@@ -323,26 +326,28 @@ public class DownloadHistoryAdapterTest { ...@@ -323,26 +326,28 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.regularItems.add(regularItem); mDownloadDelegate.regularItems.add(regularItem);
mDownloadDelegate.offTheRecordItems.add(offTheRecordItem); mDownloadDelegate.offTheRecordItems.add(offTheRecordItem);
mOfflineDelegate.items.add(offlineItem); mOfflineDelegate.items.add(offlineItem);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents(HEADER, null, offlineItem, null, regularItem, offTheRecordItem); checkAdapterContents(HEADER, null, offlineItem, null, regularItem, offTheRecordItem);
Assert.assertEquals(100011, mAdapter.getTotalDownloadSize()); Assert.assertEquals(100011, mAdapter.getTotalDownloadSize());
// Remove an item from the date bucket with two items. // Remove an item from the date bucket with two items. Wait for two callbacks as
Assert.assertEquals(1, mObserver.onChangedCallback.getCallCount()); // notifyDataSetChanged() is called once when setHeaders() is called and once when items
// are loaded.
Assert.assertEquals(2, mObserver.onChangedCallback.getCallCount());
mAdapter.onDownloadItemRemoved(offTheRecordItem.getId(), true); mAdapter.onDownloadItemRemoved(offTheRecordItem.getId(), true);
mObserver.onChangedCallback.waitForCallback(1); mObserver.onChangedCallback.waitForCallback(1);
checkAdapterContents(HEADER, null, offlineItem, null, regularItem); checkAdapterContents(HEADER, null, offlineItem, null, regularItem);
Assert.assertEquals(100001, mAdapter.getTotalDownloadSize()); Assert.assertEquals(100001, mAdapter.getTotalDownloadSize());
// Remove an item from the second bucket, which removes the bucket entirely. // Remove an item from the second bucket, which removes the bucket entirely.
Assert.assertEquals(2, mObserver.onChangedCallback.getCallCount()); Assert.assertEquals(4, mObserver.onChangedCallback.getCallCount());
mOfflineDelegate.observer.onItemDeleted(offlineItem.getGuid()); mOfflineDelegate.observer.onItemDeleted(offlineItem.getGuid());
mObserver.onChangedCallback.waitForCallback(2); mObserver.onChangedCallback.waitForCallback(2);
checkAdapterContents(HEADER, null, regularItem); checkAdapterContents(HEADER, null, regularItem);
Assert.assertEquals(1, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1, mAdapter.getTotalDownloadSize());
// Remove the last item in the list. // Remove the last item in the list.
Assert.assertEquals(3, mObserver.onChangedCallback.getCallCount()); Assert.assertEquals(6, mObserver.onChangedCallback.getCallCount());
mAdapter.onDownloadItemRemoved(regularItem.getId(), false); mAdapter.onDownloadItemRemoved(regularItem.getId(), false);
mObserver.onChangedCallback.waitForCallback(3); mObserver.onChangedCallback.waitForCallback(3);
Assert.assertEquals(0, mAdapter.getItemCount()); Assert.assertEquals(0, mAdapter.getItemCount());
...@@ -367,7 +372,7 @@ public class DownloadHistoryAdapterTest { ...@@ -367,7 +372,7 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.offTheRecordItems.add(item4); mDownloadDelegate.offTheRecordItems.add(item4);
mDownloadDelegate.regularItems.add(item5); mDownloadDelegate.regularItems.add(item5);
mOfflineDelegate.items.add(item6); mOfflineDelegate.items.add(item6);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents( checkAdapterContents(
HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0); HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0);
Assert.assertEquals(1666, mAdapter.getTotalDownloadSize()); Assert.assertEquals(1666, mAdapter.getTotalDownloadSize());
...@@ -401,7 +406,7 @@ public class DownloadHistoryAdapterTest { ...@@ -401,7 +406,7 @@ public class DownloadHistoryAdapterTest {
mOfflineDelegate.items.add(item0); mOfflineDelegate.items.add(item0);
mOfflineDelegate.items.add(item1); mOfflineDelegate.items.add(item1);
mOfflineDelegate.items.add(item2); mOfflineDelegate.items.add(item2);
initializeAdapter(false); initializeAdapter(false, true);
checkAdapterContents(HEADER, null, item2, null, item1, item0); checkAdapterContents(HEADER, null, item2, null, item1, item0);
Assert.assertEquals(111000, mAdapter.getTotalDownloadSize()); Assert.assertEquals(111000, mAdapter.getTotalDownloadSize());
...@@ -427,7 +432,7 @@ public class DownloadHistoryAdapterTest { ...@@ -427,7 +432,7 @@ public class DownloadHistoryAdapterTest {
public void testInProgress_FilePathMapAccurate() throws Exception { public void testInProgress_FilePathMapAccurate() throws Exception {
Set<DownloadHistoryItemWrapper> toDelete; Set<DownloadHistoryItemWrapper> toDelete;
initializeAdapter(false); initializeAdapter(false, false);
Assert.assertEquals(0, mAdapter.getItemCount()); Assert.assertEquals(0, mAdapter.getItemCount());
Assert.assertEquals(0, mAdapter.getTotalDownloadSize()); Assert.assertEquals(0, mAdapter.getTotalDownloadSize());
...@@ -486,7 +491,7 @@ public class DownloadHistoryAdapterTest { ...@@ -486,7 +491,7 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.offTheRecordItems.add(item4); mDownloadDelegate.offTheRecordItems.add(item4);
mDownloadDelegate.regularItems.add(item5); mDownloadDelegate.regularItems.add(item5);
mOfflineDelegate.items.add(item6); mOfflineDelegate.items.add(item6);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents( checkAdapterContents(
HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0); HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0);
...@@ -540,7 +545,7 @@ public class DownloadHistoryAdapterTest { ...@@ -540,7 +545,7 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.offTheRecordItems.add(item4); mDownloadDelegate.offTheRecordItems.add(item4);
mDownloadDelegate.regularItems.add(item5); mDownloadDelegate.regularItems.add(item5);
mOfflineDelegate.items.add(item6); mOfflineDelegate.items.add(item6);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents( checkAdapterContents(
HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0); HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0);
...@@ -586,7 +591,7 @@ public class DownloadHistoryAdapterTest { ...@@ -586,7 +591,7 @@ public class DownloadHistoryAdapterTest {
mDownloadDelegate.offTheRecordItems.add(item4); mDownloadDelegate.offTheRecordItems.add(item4);
mDownloadDelegate.regularItems.add(item5); mDownloadDelegate.regularItems.add(item5);
mOfflineDelegate.items.add(item6); mOfflineDelegate.items.add(item6);
initializeAdapter(true); initializeAdapter(true, true);
checkAdapterContents( checkAdapterContents(
HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0); HEADER, null, item5, item4, item6, null, item3, item2, null, item1, item0);
......
...@@ -34,6 +34,7 @@ import org.chromium.chrome.browser.preferences.PrefServiceBridge; ...@@ -34,6 +34,7 @@ import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.SigninManager; import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver; import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
import org.chromium.chrome.browser.widget.DateDividedAdapter;
import org.chromium.chrome.browser.widget.TintedImageButton; import org.chromium.chrome.browser.widget.TintedImageButton;
import org.chromium.chrome.browser.widget.selection.SelectableItemView; import org.chromium.chrome.browser.widget.selection.SelectableItemView;
import org.chromium.chrome.browser.widget.selection.SelectableItemViewHolder; import org.chromium.chrome.browser.widget.selection.SelectableItemViewHolder;
...@@ -442,11 +443,10 @@ public class HistoryActivityTest extends BaseActivityInstrumentationTestCase<His ...@@ -442,11 +443,10 @@ public class HistoryActivityTest extends BaseActivityInstrumentationTestCase<His
// Not signed in // Not signed in
ChromeSigninController signinController = ChromeSigninController.get(); ChromeSigninController signinController = ChromeSigninController.get();
signinController.setSignedInAccountName(null); signinController.setSignedInAccountName(null);
assertEquals(false, infoMenuItem.isVisible()); assertFalse(infoMenuItem.isVisible());
assertEquals(View.GONE, mAdapter.getSignedInNotSyncedViewForTests().getVisibility()); DateDividedAdapter.ItemGroup headerGroup = mAdapter.getFirstGroupForTests();
assertEquals(View.GONE, mAdapter.getSignedInSyncedViewForTests().getVisibility()); assertTrue(mAdapter.hasListHeader());
assertEquals( assertEquals(1, headerGroup.size());
View.GONE, mAdapter.getOtherFormsOfBrowsingHistoryViewForTests().getVisibility());
// Signed in but not synced and history has items // Signed in but not synced and history has items
signinController.setSignedInAccountName("test@gmail.com"); signinController.setSignedInAccountName("test@gmail.com");
...@@ -457,22 +457,100 @@ public class HistoryActivityTest extends BaseActivityInstrumentationTestCase<His ...@@ -457,22 +457,100 @@ public class HistoryActivityTest extends BaseActivityInstrumentationTestCase<His
toolbar.onSignInStateChange(); toolbar.onSignInStateChange();
} }
}); });
assertEquals(true, infoMenuItem.isVisible()); assertTrue(infoMenuItem.isVisible());
// Signed in, synced, has other forms and has items // Signed in, synced, has other forms and has items
// Privacy disclaimers should be shown by default // Privacy disclaimers should be shown by default
setHasOtherFormsOfBrowsingData(true, true); setHasOtherFormsOfBrowsingData(true, true);
assertEquals(true, infoMenuItem.isVisible()); assertTrue(infoMenuItem.isVisible());
assertEquals(View.VISIBLE, mAdapter.getPrivacyDisclaimersForTests().getVisibility()); headerGroup = mAdapter.getFirstGroupForTests();
assertTrue(mAdapter.hasListHeader());
assertEquals(2, headerGroup.size());
// Toggle Info Menu Item // Toggle Info Menu Item to off
ThreadUtils.runOnUiThreadBlocking(new Runnable() { ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override @Override
public void run() { public void run() {
mHistoryManager.onMenuItemClick(infoMenuItem); mHistoryManager.onMenuItemClick(infoMenuItem);
} }
}); });
assertEquals(View.GONE, mAdapter.getPrivacyDisclaimersForTests().getVisibility()); headerGroup = mAdapter.getFirstGroupForTests();
assertTrue(mAdapter.hasListHeader());
assertEquals(1, headerGroup.size());
// Toggle Info Menu Item to on
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
mHistoryManager.onMenuItemClick(infoMenuItem);
}
});
headerGroup = mAdapter.getFirstGroupForTests();
assertTrue(mAdapter.hasListHeader());
assertEquals(2, headerGroup.size());
signinController.setSignedInAccountName(null);
}
@SmallTest
public void testInfoHeaderInSearchMode() throws Exception {
final HistoryManagerToolbar toolbar = mHistoryManager.getToolbarForTests();
final MenuItem infoMenuItem = toolbar.getItemById(R.id.info_menu_id);
// Sign in
int callCount = mTestObserver.onSelectionCallback.getCallCount();
ChromeSigninController signinController = ChromeSigninController.get();
signinController.setSignedInAccountName("test@gmail.com");
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
toolbar.onSignInStateChange();
mAdapter.onSignInStateChange();
}
});
mTestObserver.onChangedCallback.waitForCallback(callCount, 1);
DateDividedAdapter.ItemGroup firstGroup = mAdapter.getFirstGroupForTests();
assertTrue(infoMenuItem.isVisible());
assertTrue(mAdapter.hasListHeader());
assertEquals(2, firstGroup.size());
// Enter search mode
callCount = mTestObserver.onSelectionCallback.getCallCount();
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0);
}
});
mTestObserver.onSelectionCallback.waitForCallback(callCount, 1);
firstGroup = mAdapter.getFirstGroupForTests();
assertFalse(infoMenuItem.isVisible());
// The first group should be the history item group from SetUp()
assertFalse(mAdapter.hasListHeader());
assertEquals(3, firstGroup.size());
signinController.setSignedInAccountName(null);
}
@SmallTest
public void testInvisibleHeader() throws Exception {
assertTrue(mAdapter.hasListHeader());
// Not sign in and set clear browsing data button to invisible
ChromeSigninController signinController = ChromeSigninController.get();
signinController.setSignedInAccountName(null);
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
mAdapter.setClearBrowsingDataButtonVisibilityForTest(false);
mAdapter.setPrivacyDisclaimerVisibility();
}
});
DateDividedAdapter.ItemGroup firstGroup = mAdapter.getFirstGroupForTests();
assertFalse(mAdapter.hasListHeader());
assertEquals(3, firstGroup.size());
} }
@SmallTest @SmallTest
......
...@@ -36,6 +36,7 @@ public class HistoryAdapterTest { ...@@ -36,6 +36,7 @@ public class HistoryAdapterTest {
public void setUp() throws Exception { public void setUp() throws Exception {
mHistoryProvider = new StubbedHistoryProvider(); mHistoryProvider = new StubbedHistoryProvider();
mAdapter = new HistoryAdapter(new SelectionDelegate<HistoryItem>(), null, mHistoryProvider); mAdapter = new HistoryAdapter(new SelectionDelegate<HistoryItem>(), null, mHistoryProvider);
mAdapter.generateHeaderItemsForTest();
} }
private void initializeAdapter() { private void initializeAdapter() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment