michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.home; michael@0: michael@0: import org.mozilla.gecko.EditBookmarkDialog; michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.GeckoEvent; michael@0: import org.mozilla.gecko.GeckoProfile; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.ReaderModeUtils; michael@0: import org.mozilla.gecko.Tabs; michael@0: import org.mozilla.gecko.Telemetry; michael@0: import org.mozilla.gecko.TelemetryContract; michael@0: import org.mozilla.gecko.db.BrowserContract.Combined; michael@0: import org.mozilla.gecko.db.BrowserDB; michael@0: import org.mozilla.gecko.favicons.Favicons; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: import org.mozilla.gecko.util.UiAsyncTask; michael@0: michael@0: import android.content.ContentResolver; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.content.res.Configuration; michael@0: import android.net.Uri; michael@0: import android.os.Bundle; michael@0: import android.support.v4.app.Fragment; michael@0: import android.util.Log; michael@0: import android.view.ContextMenu; michael@0: import android.view.ContextMenu.ContextMenuInfo; michael@0: import android.view.MenuInflater; michael@0: import android.view.MenuItem; michael@0: import android.view.View; michael@0: import android.widget.Toast; michael@0: michael@0: /** michael@0: * HomeFragment is an empty fragment that can be added to the HomePager. michael@0: * Subclasses can add their own views. michael@0: */ michael@0: abstract class HomeFragment extends Fragment { michael@0: // Log Tag. michael@0: private static final String LOGTAG="GeckoHomeFragment"; michael@0: michael@0: // Share MIME type. michael@0: protected static final String SHARE_MIME_TYPE = "text/plain"; michael@0: michael@0: // Default value for "can load" hint michael@0: static final boolean DEFAULT_CAN_LOAD_HINT = false; michael@0: michael@0: // Whether the fragment can load its content or not michael@0: // This is used to defer data loading until the editing michael@0: // mode animation ends. michael@0: private boolean mCanLoadHint; michael@0: michael@0: // Whether the fragment has loaded its content michael@0: private boolean mIsLoaded; michael@0: michael@0: @Override michael@0: public void onCreate(Bundle savedInstanceState) { michael@0: super.onCreate(savedInstanceState); michael@0: michael@0: final Bundle args = getArguments(); michael@0: if (args != null) { michael@0: mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, DEFAULT_CAN_LOAD_HINT); michael@0: } else { michael@0: mCanLoadHint = DEFAULT_CAN_LOAD_HINT; michael@0: } michael@0: michael@0: mIsLoaded = false; michael@0: } michael@0: michael@0: @Override michael@0: public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { michael@0: if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) { michael@0: return; michael@0: } michael@0: michael@0: HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo; michael@0: michael@0: // Don't show the context menu for folders. michael@0: if (info.isFolder) { michael@0: return; michael@0: } michael@0: michael@0: MenuInflater inflater = new MenuInflater(view.getContext()); michael@0: inflater.inflate(R.menu.home_contextmenu, menu); michael@0: michael@0: menu.setHeaderTitle(info.getDisplayTitle()); michael@0: michael@0: // Hide ununsed menu items. michael@0: menu.findItem(R.id.top_sites_edit).setVisible(false); michael@0: menu.findItem(R.id.top_sites_pin).setVisible(false); michael@0: menu.findItem(R.id.top_sites_unpin).setVisible(false); michael@0: michael@0: // Hide the "Edit" menuitem if this item isn't a bookmark, michael@0: // or if this is a reading list item. michael@0: if (!info.hasBookmarkId() || info.isInReadingList()) { michael@0: menu.findItem(R.id.home_edit_bookmark).setVisible(false); michael@0: } michael@0: michael@0: // Hide the "Remove" menuitem if this item not removable. michael@0: if (!info.canRemove()) { michael@0: menu.findItem(R.id.home_remove).setVisible(false); michael@0: } michael@0: michael@0: menu.findItem(R.id.home_share).setVisible(!GeckoProfile.get(getActivity()).inGuestMode()); michael@0: michael@0: final boolean canOpenInReader = (info.display == Combined.DISPLAY_READER); michael@0: menu.findItem(R.id.home_open_in_reader).setVisible(canOpenInReader); michael@0: } michael@0: michael@0: @Override michael@0: public boolean onContextItemSelected(MenuItem item) { michael@0: // onContextItemSelected() is first dispatched to the activity and michael@0: // then dispatched to its fragments. Since fragments cannot "override" michael@0: // menu item selection handling, it's better to avoid menu id collisions michael@0: // between the activity and its fragments. michael@0: michael@0: ContextMenuInfo menuInfo = item.getMenuInfo(); michael@0: if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) { michael@0: return false; michael@0: } michael@0: michael@0: final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo; michael@0: final Context context = getActivity(); michael@0: michael@0: final int itemId = item.getItemId(); michael@0: michael@0: // Track the menu action. We don't know much about the context, but we can use this to determine michael@0: // the frequency of use for various actions. michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, getResources().getResourceEntryName(itemId)); michael@0: michael@0: if (itemId == R.id.home_share) { michael@0: if (info.url == null) { michael@0: Log.e(LOGTAG, "Can't share because URL is null"); michael@0: return false; michael@0: } else { michael@0: GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "", michael@0: Intent.ACTION_SEND, info.getDisplayTitle()); michael@0: michael@0: // Context: Sharing via chrome homepage contextmenu list (home session should be active) michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (itemId == R.id.home_add_to_launcher) { michael@0: if (info.url == null) { michael@0: Log.e(LOGTAG, "Can't add to home screen because URL is null"); michael@0: return false; michael@0: } michael@0: michael@0: // Fetch an icon big enough for use as a home screen icon. michael@0: Favicons.getPreferredSizeFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle())); michael@0: return true; michael@0: } michael@0: michael@0: if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) { michael@0: if (info.url == null) { michael@0: Log.e(LOGTAG, "Can't open in new tab because URL is null"); michael@0: return false; michael@0: } michael@0: michael@0: int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND; michael@0: if (item.getItemId() == R.id.home_open_private_tab) michael@0: flags |= Tabs.LOADURL_PRIVATE; michael@0: michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU); michael@0: michael@0: final String url = (info.isInReadingList() ? ReaderModeUtils.getAboutReaderForUrl(info.url) : info.url); michael@0: michael@0: // Some pinned site items have "user-entered" urls. URLs entered in the PinSiteDialog are wrapped in michael@0: // a special URI until we can get a valid URL. If the url is a user-entered url, decode the URL before loading it. michael@0: Tabs.getInstance().loadUrl(decodeUserEnteredUrl(url), flags); michael@0: Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); michael@0: return true; michael@0: } michael@0: michael@0: if (itemId == R.id.home_edit_bookmark) { michael@0: // UI Dialog associates to the activity context, not the applications'. michael@0: new EditBookmarkDialog(context).show(info.url); michael@0: return true; michael@0: } michael@0: michael@0: if (itemId == R.id.home_open_in_reader) { michael@0: final String url = ReaderModeUtils.getAboutReaderForUrl(info.url); michael@0: Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NONE); michael@0: return true; michael@0: } michael@0: michael@0: if (itemId == R.id.home_remove) { michael@0: // Prioritize removing a history entry over a bookmark in the case of a combined item. michael@0: if (info.hasHistoryId()) { michael@0: new RemoveHistoryTask(context, info.historyId).execute(); michael@0: return true; michael@0: } michael@0: michael@0: if (info.hasBookmarkId()) { michael@0: new RemoveBookmarkTask(context, info.bookmarkId).execute(); michael@0: return true; michael@0: } michael@0: michael@0: if (info.isInReadingList()) { michael@0: (new RemoveReadingListItemTask(context, info.readingListItemId, info.url)).execute(); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public void setUserVisibleHint (boolean isVisibleToUser) { michael@0: if (isVisibleToUser == getUserVisibleHint()) { michael@0: return; michael@0: } michael@0: michael@0: super.setUserVisibleHint(isVisibleToUser); michael@0: loadIfVisible(); michael@0: } michael@0: michael@0: @Override michael@0: public void onConfigurationChanged(Configuration newConfig) { michael@0: super.onConfigurationChanged(newConfig); michael@0: } michael@0: michael@0: void setCanLoadHint(boolean canLoadHint) { michael@0: if (mCanLoadHint == canLoadHint) { michael@0: return; michael@0: } michael@0: michael@0: mCanLoadHint = canLoadHint; michael@0: loadIfVisible(); michael@0: } michael@0: michael@0: boolean getCanLoadHint() { michael@0: return mCanLoadHint; michael@0: } michael@0: michael@0: /** michael@0: * Given a url with a user-entered scheme, extract the michael@0: * scheme-specific component. For e.g, given "user-entered://www.google.com", michael@0: * this method returns "//www.google.com". If the passed url michael@0: * does not have a user-entered scheme, the same url will be returned. michael@0: * michael@0: * @param url to be decoded michael@0: * @return url component entered by user michael@0: */ michael@0: public static String decodeUserEnteredUrl(String url) { michael@0: Uri uri = Uri.parse(url); michael@0: if ("user-entered".equals(uri.getScheme())) { michael@0: return uri.getSchemeSpecificPart(); michael@0: } michael@0: return url; michael@0: } michael@0: michael@0: protected abstract void load(); michael@0: michael@0: protected boolean canLoad() { michael@0: return (mCanLoadHint && isVisible() && getUserVisibleHint()); michael@0: } michael@0: michael@0: protected void loadIfVisible() { michael@0: if (!canLoad() || mIsLoaded) { michael@0: return; michael@0: } michael@0: michael@0: load(); michael@0: mIsLoaded = true; michael@0: } michael@0: michael@0: private static class RemoveBookmarkTask extends UiAsyncTask { michael@0: private final Context mContext; michael@0: private final int mId; michael@0: michael@0: public RemoveBookmarkTask(Context context, int id) { michael@0: super(ThreadUtils.getBackgroundHandler()); michael@0: michael@0: mContext = context; michael@0: mId = id; michael@0: } michael@0: michael@0: @Override michael@0: public Void doInBackground(Void... params) { michael@0: ContentResolver cr = mContext.getContentResolver(); michael@0: BrowserDB.removeBookmark(cr, mId); michael@0: return null; michael@0: } michael@0: michael@0: @Override michael@0: public void onPostExecute(Void result) { michael@0: Toast.makeText(mContext, R.string.bookmark_removed, Toast.LENGTH_SHORT).show(); michael@0: } michael@0: } michael@0: michael@0: michael@0: private static class RemoveReadingListItemTask extends UiAsyncTask { michael@0: private final int mId; michael@0: private final String mUrl; michael@0: private final Context mContext; michael@0: michael@0: public RemoveReadingListItemTask(Context context, int id, String url) { michael@0: super(ThreadUtils.getBackgroundHandler()); michael@0: mId = id; michael@0: mUrl = url; michael@0: mContext = context; michael@0: } michael@0: michael@0: @Override michael@0: public Void doInBackground(Void... params) { michael@0: ContentResolver cr = mContext.getContentResolver(); michael@0: BrowserDB.removeReadingListItem(cr, mId); michael@0: michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: private static class RemoveHistoryTask extends UiAsyncTask { michael@0: private final Context mContext; michael@0: private final int mId; michael@0: michael@0: public RemoveHistoryTask(Context context, int id) { michael@0: super(ThreadUtils.getBackgroundHandler()); michael@0: michael@0: mContext = context; michael@0: mId = id; michael@0: } michael@0: michael@0: @Override michael@0: public Void doInBackground(Void... params) { michael@0: BrowserDB.removeHistoryEntry(mContext.getContentResolver(), mId); michael@0: return null; michael@0: } michael@0: michael@0: @Override michael@0: public void onPostExecute(Void result) { michael@0: Toast.makeText(mContext, R.string.history_removed, Toast.LENGTH_SHORT).show(); michael@0: } michael@0: } michael@0: }