/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.linkbubble.ui; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.linkbubble.Config; import com.linkbubble.Constant; import com.linkbubble.MainApplication; import com.linkbubble.MainController; import com.linkbubble.R; import com.linkbubble.Settings; import com.linkbubble.db.DatabaseHelper; import com.linkbubble.db.HistoryRecord; import com.linkbubble.util.ActionItem; import com.linkbubble.util.Analytics; import com.linkbubble.util.Util; import com.squareup.otto.Subscribe; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.favicons.LoadFaviconTask; import org.mozilla.gecko.favicons.OnFaviconLoadedListener; import org.mozilla.gecko.widget.FaviconView; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; public class HistoryActivity extends AppCompatActivity implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener { private TextView mMessageView; private ListView mListView; private HistoryAdapter mHistoryAdapter; private List<HistoryRecord> mHistoryRecords; private static int sInstanceCount = 0; private static Favicons sFavicons = null; private static final int FAVICON_CACHE_SIZE = 4 * 1024 * 1024; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sInstanceCount++; if (sInstanceCount == 1) { sFavicons = new Favicons(FAVICON_CACHE_SIZE); } setContentView(R.layout.activity_history); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mMessageView = (TextView) findViewById(R.id.message_view); mListView = (ListView) findViewById(R.id.listview); } @Override protected void onDestroy() { sInstanceCount--; if (sInstanceCount == 0) { sFavicons = null; } super.onDestroy(); } @Override public void onStart() { super.onStart(); mHistoryRecords = MainApplication.sDatabaseHelper.getAllHistoryRecords(); setupListView(); ((MainApplication)getApplicationContext()).getBus().register(this); MainApplication.checkRestoreCurrentTabs(this); } @Override public void onStop() { super.onStop(); ((MainApplication)getApplicationContext()).getBus().unregister(this); } private void setupListView() { if (mHistoryRecords == null || mHistoryRecords.size() == 0) { showNoHistoryView(); return; } mMessageView.setVisibility(View.GONE); mHistoryAdapter = new HistoryAdapter(this); mListView.setAdapter(mHistoryAdapter); mListView.setOnItemClickListener(this); mListView.setOnItemLongClickListener(this); final SwipeDismissListViewTouchListener swipeDismissTouchListener = new SwipeDismissListViewTouchListener( mListView, new SwipeDismissListViewTouchListener.DismissCallbacks() { public boolean canDismiss(int position) { if (mHistoryRecords != null && position < mHistoryRecords.size()) { return true; } return false; } public void onDismiss(ListView listView, int[] reverseSortedPositions) { DatabaseHelper databaseHelper = MainApplication.sDatabaseHelper; for (int position : reverseSortedPositions) { Object item = listView.getItemAtPosition(position); if (item instanceof HistoryRecord) { if (databaseHelper.deleteHistoryRecord((HistoryRecord)item)) { mHistoryRecords.remove(item); } } } if (mHistoryRecords.size() == 0) { showNoHistoryView(); } else { if (mHistoryAdapter != null) { mHistoryAdapter.notifyDataSetChanged(); } } } }); mListView.setOnItemClickListener(this); mListView.setOnScrollListener(swipeDismissTouchListener.makeScrollListener()); mListView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { return swipeDismissTouchListener.onTouch(view, motionEvent); } }); mListView.setItemsCanFocus(true); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.history_activity, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; case R.id.action_clear_history: { if (mHistoryAdapter == null) { Toast.makeText(this, R.string.history_already_empty, Toast.LENGTH_SHORT).show(); return true; } final AlertDialog alertDialog = new AlertDialog.Builder(this).create(); alertDialog.setTitle(R.string.erase_all_history_title); alertDialog.setMessage(getString(R.string.erase_all_history_message)); alertDialog.setCancelable(true); alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MainApplication.sDatabaseHelper.deleteAllHistoryRecords(); mHistoryRecords = null; if (mHistoryAdapter != null) { mHistoryAdapter.notifyDataSetChanged(); } } }); alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); Util.showThemedDialog(alertDialog); return true; } } return super.onOptionsItemSelected(item); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (view.getTag() instanceof HistoryItem) { HistoryItem historyItem = (HistoryItem)view.getTag(); MainApplication.openLink(this, historyItem.mHistoryRecord.getUrl(), Analytics.OPENED_URL_FROM_HISTORY); } } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (view.getTag() instanceof HistoryItem) { final HistoryItem historyItem = (HistoryItem)view.getTag(); Resources resources = getResources(); final ArrayList<String> longClickSelections = new ArrayList<String>(); final String shareLabel = resources.getString(R.string.action_share); longClickSelections.add(shareLabel); String defaultBrowserLabel = Settings.get().getDefaultBrowserLabel(); final String leftConsumeBubbleLabel = Settings.get().getConsumeBubbleLabel(Constant.BubbleAction.ConsumeLeft); if (leftConsumeBubbleLabel != null) { if (defaultBrowserLabel == null || defaultBrowserLabel.equals(leftConsumeBubbleLabel) == false) { longClickSelections.add(leftConsumeBubbleLabel); } } final String rightConsumeBubbleLabel = Settings.get().getConsumeBubbleLabel(Constant.BubbleAction.ConsumeRight); if (rightConsumeBubbleLabel != null) { if (defaultBrowserLabel == null || defaultBrowserLabel.equals(rightConsumeBubbleLabel) == false) { longClickSelections.add(rightConsumeBubbleLabel); } } final String copyLinkLabel = resources.getString(R.string.action_copy_to_clipboard); longClickSelections.add(copyLinkLabel); Collections.sort(longClickSelections); final String openInNewBubbleLabel = resources.getString(R.string.action_open_in_new_bubble); longClickSelections.add(0, openInNewBubbleLabel); final String openInBrowserLabel = defaultBrowserLabel != null ? String.format(resources.getString(R.string.action_open_in_browser), defaultBrowserLabel) : null; if (openInBrowserLabel != null) { longClickSelections.add(1, openInBrowserLabel); } final AlertDialog longPressAlertDialog = new AlertDialog.Builder(this).create(); ListView listView = new ListView(this); listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, longClickSelections.toArray(new String[0]))); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String string = longClickSelections.get(position); String urlAsString = historyItem.mHistoryRecord.getUrl(); if (string.equals(openInNewBubbleLabel)) { if (MainController.get() != null) { MainController.get().openUrl(urlAsString, System.currentTimeMillis(), false, Analytics.OPENED_URL_FROM_HISTORY); } else { MainApplication.openLink(getApplicationContext(), urlAsString, Analytics.OPENED_URL_FROM_HISTORY); } } else if (openInBrowserLabel != null && string.equals(openInBrowserLabel)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(urlAsString)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); MainApplication.openInBrowser(HistoryActivity.this, intent, true, false); } else if (string.equals(shareLabel)) { AlertDialog alertDialog = ActionItem.getShareAlert(HistoryActivity.this, false, new ActionItem.OnActionItemSelectedListener() { @Override public void onSelected(ActionItem actionItem) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.setClassName(actionItem.mPackageName, actionItem.mActivityClassName); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Intent.EXTRA_TEXT, historyItem.mHistoryRecord.getUrl()); startActivity(intent); } }); Util.showThemedDialog(alertDialog); } else if (leftConsumeBubbleLabel != null && string.equals(leftConsumeBubbleLabel)) { MainApplication.handleBubbleAction(HistoryActivity.this, Constant.BubbleAction.ConsumeLeft, urlAsString, -1); } else if (rightConsumeBubbleLabel != null && string.equals(rightConsumeBubbleLabel)) { MainApplication.handleBubbleAction(HistoryActivity.this, Constant.BubbleAction.ConsumeRight, urlAsString, -1); } else if (string.equals(copyLinkLabel)) { MainApplication.copyLinkToClipboard(HistoryActivity.this, urlAsString, R.string.link_copied_to_clipboard); } if (longPressAlertDialog != null) { longPressAlertDialog.dismiss(); } } }); longPressAlertDialog.setView(listView); Util.showThemedDialog(longPressAlertDialog); return true; } return false; } void showNoHistoryView() { mMessageView.setVisibility(View.VISIBLE); mMessageView.setText(R.string.empty); } private class HistoryAdapter extends BaseAdapter { LayoutInflater mInflater; public HistoryAdapter(Context context) { mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return mHistoryRecords != null ? mHistoryRecords.size() + 1 : 0; } @Override public Object getItem(int position) { return mHistoryRecords != null ? mHistoryRecords.get(position) : position; } @Override public long getItemId(int position) { return position; } @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { return position == mHistoryRecords.size() ? 1 : 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (position == mHistoryRecords.size()) { TextView noMoreView; if (convertView == null || convertView instanceof TextView == false) { noMoreView = new TextView(HistoryActivity.this); noMoreView.setGravity(Gravity.CENTER); noMoreView.setText("○"); noMoreView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Config.dpToPx(40))); } else { noMoreView = (TextView)convertView; } return noMoreView; } HistoryItem historyItem; HistoryRecord historyRecord = mHistoryRecords.get(position); if (convertView == null) { convertView = mInflater.inflate(R.layout.history_item, parent, false); historyItem = new HistoryItem(); historyItem.mTitleTextView = (TextView) convertView.findViewById(R.id.page_title); historyItem.mUrlTextView = (TextView) convertView.findViewById(R.id.page_url); historyItem.mTimeTextView = (TextView) convertView.findViewById(R.id.page_date); historyItem.mFaviconImageView = (FaviconView) convertView.findViewById(R.id.favicon); historyItem.mFaviconImageView.mFavicons = sFavicons; } else { historyItem = (HistoryItem) convertView.getTag(); } historyItem.mHistoryRecord = historyRecord; historyItem.mDate.setTime(historyRecord.getTime()); historyItem.mFaviconSet = false; historyItem.mTitleTextView.setText(historyRecord.getTitle()); historyItem.mUrlTextView.setText(historyRecord.getHost()); historyItem.mTimeTextView.setText(Util.getPrettyDate(historyItem.mDate)); int flags = Settings.get().isIncognitoMode() ? 0 : LoadFaviconTask.FLAG_PERSIST; String host = historyRecord.getHost(); String faviconUrl = "http://" + host + "/favicon.ico"; historyItem.mFaviconUrl = faviconUrl; historyItem.mFaviconImageView.clearImage(); sFavicons.getFaviconForSize(host, faviconUrl, Integer.MAX_VALUE, flags, historyItem.mOnFaviconLoadedListener); if (historyItem.mFaviconSet == false) { historyItem.mFaviconImageView.showDefaultFavicon(); } convertView.setTag(historyItem); return convertView; } } private static class HistoryItem { TextView mTitleTextView; TextView mUrlTextView; TextView mTimeTextView; FaviconView mFaviconImageView; HistoryRecord mHistoryRecord; Date mDate = new Date(); String mFaviconUrl; boolean mFaviconSet; OnFaviconLoadedListener mOnFaviconLoadedListener = new OnFaviconLoadedListener() { @Override public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { // Ensure the favicon passed in matches the one we want. This can be false as HistoryAdapter recycles // Views and favicons are loaded in different orders to that which they are requested. if (mFaviconUrl.equals(faviconURL) == false) { return; } if (favicon != null) { mFaviconSet = true; mFaviconImageView.updateImage(favicon, faviconURL, true); } } }; } @SuppressWarnings("unused") @Subscribe public void onHistoryRecordChangedEvent(HistoryRecord.ChangedEvent event) { boolean setupList = false; if (mHistoryRecords == null) { mHistoryRecords = new ArrayList<HistoryRecord>(); setupList = true; } HistoryRecord historyRecord = event.mHistoryRecord; // find out if the item exists on the list already. This will be true if a HistoryRecord for a URL was updated boolean onList = false; for (HistoryRecord existing : mHistoryRecords) { if (existing.getId() == historyRecord.getId()) { mHistoryRecords.remove(existing); break; } } // Add it at the top of the list. This assumes the item had it's date updated to 'now', // which is the current behaviour. mHistoryRecords.add(0, historyRecord); if (setupList) { setupListView(); } else { if (mMessageView != null) { mMessageView.setVisibility(View.GONE); } if (mHistoryAdapter != null) { mHistoryAdapter.notifyDataSetChanged(); } } } }