Commit a6ba8d8c authored by twellington's avatar twellington Committed by Commit bot

[Android History] Add clear browsing data button and privacy disclaimers

Adds an optional header to DateDividedAdapter and adds a new layout
with a clear browsing data button and privacy disclaimers as a header
in the history ui.

BUG=654071

Review-Url: https://codereview.chromium.org/2562123003
Cr-Commit-Position: refs/heads/master@{#437931}
parent 7fb93a52
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 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" >
<TextView
android:id="@+id/signed_in_not_synced"
style="@style/PrivacyDisclaimerText" />
<TextView
android:id="@+id/signed_in_synced"
style="@style/PrivacyDisclaimerText" />
<TextView
android:id="@+id/other_forms_of_browsing_history"
style="@style/PrivacyDisclaimerText" />
<Button
android:id="@+id/clear_browsing_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
style="@style/ButtonCompatBorderless"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center_vertical|start"
android:text="@string/open_clear_browsing_data_dialog_button"
android:textAllCaps="true"
android:textColor="@color/light_active_color"
android:textSize="16sp" />
</LinearLayout>
\ No newline at end of file
......@@ -582,6 +582,15 @@
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">20sp</item>
</style>
<style name="PrivacyDisclaimerText">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">16dp</item>
<item name="android:textSize">16sp</item>
<item name="android:visibility">gone</item>
</style>
<!-- New tab page RecyclerView overscroll color -->
<style name="NewTabPageRecyclerView">
......
......@@ -340,6 +340,7 @@ public class DownloadHistoryAdapter extends DateDividedAdapter implements Downlo
mRegularDownloadItems.filter(mFilter, mFilteredItems);
mIncognitoDownloadItems.filter(mFilter, mFilteredItems);
mOfflinePageItems.filter(mFilter, mFilteredItems);
clear(false);
loadItems(mFilteredItems);
}
......
......@@ -30,6 +30,14 @@ public class BrowsingHistoryBridge {
* via this method.
*/
public void onHistoryDeleted();
/**
* Called after querying history to indicate whether other forms of browsing history were
* found.
* @param hasOtherForms Whether other forms of browsing history were found.
* @param hasSyncedResults Whether synced results were found.
*/
public void hasOtherFormsOfBrowsingData(boolean hasOtherForms, boolean hasSyncedResults);
}
private final BrowsingHistoryObserver mObserver;
......@@ -114,6 +122,11 @@ public class BrowsingHistoryBridge {
mObserver.onHistoryDeleted();
}
@CalledByNative
public void hasOtherFormsOfBrowsingData(boolean hasOtherForms, boolean hasSyncedResults) {
mObserver.hasOtherFormsOfBrowsingData(hasOtherForms, hasSyncedResults);
}
private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeBrowsingHistoryBridge);
private native void nativeQueryHistory(long nativeBrowsingHistoryBridge,
......
......@@ -5,28 +5,48 @@
package org.chromium.chrome.browser.history;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.history.BrowsingHistoryBridge.BrowsingHistoryObserver;
import org.chromium.chrome.browser.widget.DateDividedAdapter;
import org.chromium.chrome.browser.widget.selection.SelectableItemViewHolder;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;
import java.util.List;
import java.util.Locale;
/**
* Bridges the user's browsing history and the UI used to display it.
*/
public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistoryObserver {
private static final String EMPTY_QUERY = "";
private static final String LEARN_MORE_LINK =
"https://support.google.com/chrome/?p=sync_history&amp;hl="
+ Locale.getDefault().toString();
private static final String GOOGLE_HISTORY_LINK = "history.google.com";
private final SelectionDelegate<HistoryItem> mSelectionDelegate;
private final BrowsingHistoryBridge mBridge;
private final HistoryManager mManager;
private TextView mSignedInNotSyncedTextView;
private TextView mSignedInSyncedTextView;
private TextView mOtherFormsOfBrowsingHistoryTextView;
private boolean mHasOtherFormsOfBrowsingData;
private boolean mHasSyncedData;
private boolean mHeaderInflated;
private boolean mDestroyed;
public HistoryAdapter(SelectionDelegate<HistoryItem> delegate, HistoryManager manager) {
......@@ -101,7 +121,11 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
// destroyed to avoid unnecessary work.
if (mDestroyed) return;
loadItems(items);
clear(true);
if (items.size() > 0) {
addHeader();
loadItems(items);
}
}
@Override
......@@ -111,4 +135,69 @@ public class HistoryAdapter extends DateDividedAdapter implements BrowsingHistor
// This currently removes all items and re-issues a query.
initialize();
}
@Override
public void hasOtherFormsOfBrowsingData(boolean hasOtherForms, boolean hasSyncedResults) {
mHasOtherFormsOfBrowsingData = hasOtherForms;
mHasSyncedData = hasSyncedResults;
setPrivacyDisclaimerVisibility();
}
@Override
protected HeaderViewHolder createHeader(ViewGroup parent) {
ViewGroup v = (ViewGroup) LayoutInflater.from(parent.getContext()).inflate(
R.layout.history_header, parent, false);
mHeaderInflated = true;
View cbdButton = v.findViewById(R.id.clear_browsing_data_button);
cbdButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mManager.openClearBrowsingDataPreference();
}
});
mSignedInNotSyncedTextView = (TextView) v.findViewById(R.id.signed_in_not_synced);
setPrivacyDisclaimerText(mSignedInNotSyncedTextView,
R.string.android_history_no_synced_results, LEARN_MORE_LINK);
mSignedInSyncedTextView = (TextView) v.findViewById(R.id.signed_in_synced);
setPrivacyDisclaimerText(mSignedInSyncedTextView,
R.string.android_history_has_synced_results, LEARN_MORE_LINK);
mOtherFormsOfBrowsingHistoryTextView = (TextView) v.findViewById(
R.id.other_forms_of_browsing_history);
setPrivacyDisclaimerText(mOtherFormsOfBrowsingHistoryTextView,
R.string.android_history_other_forms_of_history, GOOGLE_HISTORY_LINK);
setPrivacyDisclaimerVisibility();
return new HeaderViewHolder(v);
}
private void setPrivacyDisclaimerText(TextView view, int stringId, final String url) {
NoUnderlineClickableSpan link = new NoUnderlineClickableSpan() {
@Override
public void onClick(View view) {
mManager.openUrl(url, null, true);
}
};
SpannableString spannable = SpanApplier.applySpans(
view.getResources().getString(stringId),
new SpanApplier.SpanInfo("<link>", "</link>", link));
view.setText(spannable);
view.setMovementMethod(LinkMovementMethod.getInstance());
}
private void setPrivacyDisclaimerVisibility() {
if (!mHeaderInflated) return;
boolean isSignedIn =
ChromeSigninController.get(ContextUtils.getApplicationContext()).isSignedIn();
mSignedInNotSyncedTextView.setVisibility(
!mHasSyncedData && isSignedIn ? View.VISIBLE : View.GONE);
mSignedInSyncedTextView.setVisibility(mHasSyncedData ? View.VISIBLE : View.GONE);
mOtherFormsOfBrowsingHistoryTextView.setVisibility(
mHasOtherFormsOfBrowsingData ? View.VISIBLE : View.GONE);
}
}
......@@ -85,7 +85,7 @@ public class HistoryItem extends TimedItem {
* Navigates a tab to this item's URL.
*/
public void open() {
if (mManager != null) mManager.openItem(mUrl, null, false);
if (mManager != null) mManager.openUrl(mUrl, null, false);
}
/**
......
......@@ -17,6 +17,8 @@ import android.view.ViewGroup;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.preferences.PreferencesLauncher;
import org.chromium.chrome.browser.preferences.privacy.ClearBrowsingDataPreferences;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.widget.TintedDrawable;
import org.chromium.chrome.browser.widget.selection.SelectableListLayout;
......@@ -114,14 +116,14 @@ public class HistoryManager implements OnMenuItemClickListener {
}
/**
* Open the history item.
* @param url The URL of the history item.
* @param isIncognito Whether to open the history item in an incognito tab. If null, the tab
* Open the provided url.
* @param url The url to open.
* @param isIncognito Whether to open the url in an incognito tab. If null, the tab
* will open in the current tab model.
* @param createNewTab Whether a new tab should be created. If false, the item will clobber the
* the current tab.
*/
public void openItem(String url, Boolean isIncognito, boolean createNewTab) {
public void openUrl(String url, Boolean isIncognito, boolean createNewTab) {
// Construct basic intent.
Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
viewIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
......@@ -151,9 +153,18 @@ public class HistoryManager implements OnMenuItemClickListener {
IntentHandler.startActivityForTrustedIntent(viewIntent, mActivity);
}
/**
* Opens the clear browsing data preference.
*/
public void openClearBrowsingDataPreference() {
Intent intent = PreferencesLauncher.createIntentForSettingsPage(mActivity,
ClearBrowsingDataPreferences.class.getName());
IntentUtils.safeStartActivity(mActivity, intent);
}
private void openItemsInNewTabs(List<HistoryItem> items, boolean isIncognito) {
for (HistoryItem item : items) {
openItem(item.getUrl(), isIncognito, true);
openUrl(item.getUrl(), isIncognito, true);
}
}
}
\ No newline at end of file
......@@ -105,6 +105,12 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
}
}
protected static class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}
/**
* A bucket of items with the same date.
*/
......@@ -116,16 +122,16 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
private int mIndex;
private boolean mIsSorted;
private boolean mIsListHeader;
public ItemGroup(TimedItem item) {
mDate = new Date(item.getTimestamp());
mItems.add(item);
public ItemGroup(long timestamp) {
mDate = new Date(timestamp);
mIsSorted = true;
}
public void addItem(TimedItem item) {
mItems.add(item);
mIsSorted = false;
mIsSorted = mItems.size() == 1;
}
public void removeItem(TimedItem item) {
......@@ -161,13 +167,15 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
* @return The size of this group.
*/
public int size() {
if (mIsListHeader) return 1;
// Plus 1 to account for the date header.
return mItems.size() + 1;
}
public TimedItem getItemAt(int index) {
// 0 is allocated to the date header.
if (index == 0) return null;
// 0 is allocated to the date header. The list header has no items.
if (index == 0 || mIsListHeader) return null;
sortIfNeeded();
return mItems.get(index - 1);
......@@ -203,10 +211,12 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar();
private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar();
public static final int TYPE_HEADER = -1;
public static final int TYPE_DATE = 0;
public static final int TYPE_NORMAL = 1;
private int mSize;
private boolean mHasListHeader;
private SortedSet<ItemGroup> mGroups = new TreeSet<>(new Comparator<ItemGroup>() {
@Override
public int compare(ItemGroup lhs, ItemGroup rhs) {
......@@ -220,6 +230,14 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
*/
protected abstract ViewHolder createViewHolder(ViewGroup parent);
/**
* Creates a {@link HeaderViewHolder} in the given parent.
* @see #onCreateViewHolder(ViewGroup, int)
*/
protected HeaderViewHolder createHeader(ViewGroup parent) {
return null;
}
/**
* Binds the {@link ViewHolder} with the given {@link TimedItem}.
* @see #onBindViewHolder(ViewHolder, int)
......@@ -233,15 +251,10 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
protected abstract int getTimedItemViewResId();
/**
* Loads a list of {@link TimedItem}s to this adapter. Any previous data will be removed.
* Loads a list of {@link TimedItem}s to this adapter. Previous data will not be removed. Call
* {@link #clear(boolean)} to remove previous items.
*/
public void loadItems(List<? extends TimedItem> timedItems) {
mSize = 0;
// Unset the positions of all items in the list.
for (ItemGroup group : mGroups) group.resetPosition();
mGroups.clear();
for (TimedItem timedItem : timedItems) {
Date date = new Date(timedItem.getTimestamp());
boolean found = false;
......@@ -256,7 +269,9 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
if (!found) {
// Create a new ItemGroup with the date for the new item. This increases the
// size by two because we add new views for the date and the item itself.
mGroups.add(new ItemGroup(timedItem));
ItemGroup newGroup = new ItemGroup(timedItem.getTimestamp());
newGroup.addItem(timedItem);
mGroups.add(newGroup);
mSize += 2;
}
}
......@@ -271,13 +286,33 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
notifyDataSetChanged();
}
/**
* Adds a header as the first group in this adapter.
*/
public void addHeader() {
assert mSize == 0;
ItemGroup header = new ItemGroup(Long.MAX_VALUE);
header.mIsListHeader = true;
mGroups.add(header);
mSize++;
mHasListHeader = true;
}
/**
* Removes all items from this adapter.
* @param notifyDataSetChanged Whether to notify that the data set has been changed.
*/
public void clear() {
public void clear(boolean notifyDataSetChanged) {
mSize = 0;
mHasListHeader = false;
// Unset the positions of all items in the list.
for (ItemGroup group : mGroups) group.resetPosition();
mGroups.clear();
notifyDataSetChanged();
if (notifyDataSetChanged) notifyDataSetChanged();
}
@Override
......@@ -289,8 +324,8 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
}
/**
* Gets the item at the given position. For date headers, {@link TimedItem} will be null; for
* normal items, {@link Date} will be null.
* Gets the item at the given position. For date headers and the list header, {@link TimedItem}
* will be null; for normal items, {@link Date} will be null.
*/
public Pair<Date, TimedItem> getItemAt(int position) {
Pair<ItemGroup, Integer> pair = getGroupAt(position);
......@@ -301,7 +336,13 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
@Override
public final int getItemViewType(int position) {
Pair<ItemGroup, Integer> pair = getGroupAt(position);
return pair.second == 0 ? TYPE_DATE : TYPE_NORMAL;
if (pair.second == TYPE_HEADER) {
return TYPE_HEADER;
} else if (pair.second == 0) {
return TYPE_DATE;
} else {
return TYPE_NORMAL;
}
}
@Override
......@@ -311,7 +352,10 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
getTimedItemViewResId(), parent, false));
} else if (viewType == TYPE_NORMAL) {
return createViewHolder(parent);
} else if (viewType == TYPE_HEADER) {
return createHeader(parent);
}
assert false;
return null;
}
......@@ -320,7 +364,7 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
Pair<Date, TimedItem> pair = getItemAt(position);
if (holder instanceof DateViewHolder) {
((DateViewHolder) holder).setDate(pair.first);
} else {
} else if (!(holder instanceof HeaderViewHolder)) {
bindViewHolderForTimedItem(holder, pair.second);
}
}
......@@ -335,6 +379,10 @@ public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder
*/
protected Pair<ItemGroup, Integer> getGroupAt(int position) {
// TODO(ianwen): Optimize the performance if the number of groups becomes too large.
if (mHasListHeader && position == 0) {
return new Pair<>(mGroups.first(), TYPE_HEADER);
}
int i = position;
for (ItemGroup group : mGroups) {
if (i >= group.size()) {
......
......@@ -537,6 +537,18 @@ Your Google account may have other forms of browsing history like searches and a
<message name="IDS_CLEAR_BROWSING_DATA_IMPORTANT_DIALOG_BUTTON" desc="The text of the button to perform the clear action in the dialog presenting the user with 'important' sites that they can exclude from clearing.">
Clear
</message>
<message name="IDS_OPEN_CLEAR_BROWSING_DATA_DIALOG_BUTTON" desc="Title of the button that will open the clear browsing data dialog.">
Clear browsing data…
</message>
<message name="IDS_ANDROID_HISTORY_NO_SYNCED_RESULTS" desc="The notification at the top of the history page indicating that it does not include visits from other devices.">
Showing history from this device. <ph name="BEGIN_LINK">&lt;link&gt;</ph>Learn more<ph name="END_LINK">&lt;/link&gt;</ph>.
</message>
<message name="IDS_ANDROID_HISTORY_HAS_SYNCED_RESULTS" desc="The notification at the top of the history page indicating that it is showing visits synced from other devices.">
Showing history from your signed-in devices. <ph name="BEGIN_LINK">&lt;link&gt;</ph>Learn more<ph name="END_LINK">&lt;/link&gt;</ph>.
</message>
<message name="IDS_ANDROID_HISTORY_OTHER_FORMS_OF_HISTORY" desc="The notification at the top of the history page indicating that deleting Chrome browsing history will not delete other forms of history stored at Google My Activity.">
Your Google Account may have other forms of browsing history at <ph name="BEGIN_LINK">&lt;link&gt;</ph>history.google.com<ph name="END_LINK">&lt;/link&gt;</ph>.
</message>
<message name="IDS_USAGE_AND_CRASH_REPORTS_TITLE" desc="Title for 'Usage and crash reports' preference">
Usage and crash reports
......
......@@ -43,7 +43,11 @@ void BrowsingHistoryBridge::QueryHistory(
history::QueryOptions options;
options.max_count = kMaxQueryCount;
options.end_time = base::Time::FromJavaTime(j_query_end_time);
if (j_query_end_time == 0) {
options.end_time = base::Time();
} else {
options.end_time = base::Time::FromJavaTime(j_query_end_time);
}
options.duplicate_policy = history::QueryOptions::REMOVE_DUPLICATES_PER_DAY;
browsing_history_service_->QueryHistory(
......@@ -138,7 +142,9 @@ void BrowsingHistoryBridge::HistoryDeleted() {
void BrowsingHistoryBridge::HasOtherFormsOfBrowsingHistory(
bool has_other_forms, bool has_synced_results) {
// TODO(twellington): implement
JNIEnv* env = base::android::AttachCurrentThread();
Java_BrowsingHistoryBridge_hasOtherFormsOfBrowsingData(
env, j_history_service_obj_.obj(), has_other_forms, has_synced_results);
}
bool RegisterBrowsingHistoryBridge(JNIEnv* env) {
......
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