1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/home/HomeFragment.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,345 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.home; 1.10 + 1.11 +import org.mozilla.gecko.EditBookmarkDialog; 1.12 +import org.mozilla.gecko.GeckoAppShell; 1.13 +import org.mozilla.gecko.GeckoEvent; 1.14 +import org.mozilla.gecko.GeckoProfile; 1.15 +import org.mozilla.gecko.R; 1.16 +import org.mozilla.gecko.ReaderModeUtils; 1.17 +import org.mozilla.gecko.Tabs; 1.18 +import org.mozilla.gecko.Telemetry; 1.19 +import org.mozilla.gecko.TelemetryContract; 1.20 +import org.mozilla.gecko.db.BrowserContract.Combined; 1.21 +import org.mozilla.gecko.db.BrowserDB; 1.22 +import org.mozilla.gecko.favicons.Favicons; 1.23 +import org.mozilla.gecko.util.ThreadUtils; 1.24 +import org.mozilla.gecko.util.UiAsyncTask; 1.25 + 1.26 +import android.content.ContentResolver; 1.27 +import android.content.Context; 1.28 +import android.content.Intent; 1.29 +import android.content.res.Configuration; 1.30 +import android.net.Uri; 1.31 +import android.os.Bundle; 1.32 +import android.support.v4.app.Fragment; 1.33 +import android.util.Log; 1.34 +import android.view.ContextMenu; 1.35 +import android.view.ContextMenu.ContextMenuInfo; 1.36 +import android.view.MenuInflater; 1.37 +import android.view.MenuItem; 1.38 +import android.view.View; 1.39 +import android.widget.Toast; 1.40 + 1.41 +/** 1.42 + * HomeFragment is an empty fragment that can be added to the HomePager. 1.43 + * Subclasses can add their own views. 1.44 + */ 1.45 +abstract class HomeFragment extends Fragment { 1.46 + // Log Tag. 1.47 + private static final String LOGTAG="GeckoHomeFragment"; 1.48 + 1.49 + // Share MIME type. 1.50 + protected static final String SHARE_MIME_TYPE = "text/plain"; 1.51 + 1.52 + // Default value for "can load" hint 1.53 + static final boolean DEFAULT_CAN_LOAD_HINT = false; 1.54 + 1.55 + // Whether the fragment can load its content or not 1.56 + // This is used to defer data loading until the editing 1.57 + // mode animation ends. 1.58 + private boolean mCanLoadHint; 1.59 + 1.60 + // Whether the fragment has loaded its content 1.61 + private boolean mIsLoaded; 1.62 + 1.63 + @Override 1.64 + public void onCreate(Bundle savedInstanceState) { 1.65 + super.onCreate(savedInstanceState); 1.66 + 1.67 + final Bundle args = getArguments(); 1.68 + if (args != null) { 1.69 + mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, DEFAULT_CAN_LOAD_HINT); 1.70 + } else { 1.71 + mCanLoadHint = DEFAULT_CAN_LOAD_HINT; 1.72 + } 1.73 + 1.74 + mIsLoaded = false; 1.75 + } 1.76 + 1.77 + @Override 1.78 + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 1.79 + if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) { 1.80 + return; 1.81 + } 1.82 + 1.83 + HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo; 1.84 + 1.85 + // Don't show the context menu for folders. 1.86 + if (info.isFolder) { 1.87 + return; 1.88 + } 1.89 + 1.90 + MenuInflater inflater = new MenuInflater(view.getContext()); 1.91 + inflater.inflate(R.menu.home_contextmenu, menu); 1.92 + 1.93 + menu.setHeaderTitle(info.getDisplayTitle()); 1.94 + 1.95 + // Hide ununsed menu items. 1.96 + menu.findItem(R.id.top_sites_edit).setVisible(false); 1.97 + menu.findItem(R.id.top_sites_pin).setVisible(false); 1.98 + menu.findItem(R.id.top_sites_unpin).setVisible(false); 1.99 + 1.100 + // Hide the "Edit" menuitem if this item isn't a bookmark, 1.101 + // or if this is a reading list item. 1.102 + if (!info.hasBookmarkId() || info.isInReadingList()) { 1.103 + menu.findItem(R.id.home_edit_bookmark).setVisible(false); 1.104 + } 1.105 + 1.106 + // Hide the "Remove" menuitem if this item not removable. 1.107 + if (!info.canRemove()) { 1.108 + menu.findItem(R.id.home_remove).setVisible(false); 1.109 + } 1.110 + 1.111 + menu.findItem(R.id.home_share).setVisible(!GeckoProfile.get(getActivity()).inGuestMode()); 1.112 + 1.113 + final boolean canOpenInReader = (info.display == Combined.DISPLAY_READER); 1.114 + menu.findItem(R.id.home_open_in_reader).setVisible(canOpenInReader); 1.115 + } 1.116 + 1.117 + @Override 1.118 + public boolean onContextItemSelected(MenuItem item) { 1.119 + // onContextItemSelected() is first dispatched to the activity and 1.120 + // then dispatched to its fragments. Since fragments cannot "override" 1.121 + // menu item selection handling, it's better to avoid menu id collisions 1.122 + // between the activity and its fragments. 1.123 + 1.124 + ContextMenuInfo menuInfo = item.getMenuInfo(); 1.125 + if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) { 1.126 + return false; 1.127 + } 1.128 + 1.129 + final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo; 1.130 + final Context context = getActivity(); 1.131 + 1.132 + final int itemId = item.getItemId(); 1.133 + 1.134 + // Track the menu action. We don't know much about the context, but we can use this to determine 1.135 + // the frequency of use for various actions. 1.136 + Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, getResources().getResourceEntryName(itemId)); 1.137 + 1.138 + if (itemId == R.id.home_share) { 1.139 + if (info.url == null) { 1.140 + Log.e(LOGTAG, "Can't share because URL is null"); 1.141 + return false; 1.142 + } else { 1.143 + GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "", 1.144 + Intent.ACTION_SEND, info.getDisplayTitle()); 1.145 + 1.146 + // Context: Sharing via chrome homepage contextmenu list (home session should be active) 1.147 + Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST); 1.148 + return true; 1.149 + } 1.150 + } 1.151 + 1.152 + if (itemId == R.id.home_add_to_launcher) { 1.153 + if (info.url == null) { 1.154 + Log.e(LOGTAG, "Can't add to home screen because URL is null"); 1.155 + return false; 1.156 + } 1.157 + 1.158 + // Fetch an icon big enough for use as a home screen icon. 1.159 + Favicons.getPreferredSizeFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle())); 1.160 + return true; 1.161 + } 1.162 + 1.163 + if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) { 1.164 + if (info.url == null) { 1.165 + Log.e(LOGTAG, "Can't open in new tab because URL is null"); 1.166 + return false; 1.167 + } 1.168 + 1.169 + int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND; 1.170 + if (item.getItemId() == R.id.home_open_private_tab) 1.171 + flags |= Tabs.LOADURL_PRIVATE; 1.172 + 1.173 + Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU); 1.174 + 1.175 + final String url = (info.isInReadingList() ? ReaderModeUtils.getAboutReaderForUrl(info.url) : info.url); 1.176 + 1.177 + // Some pinned site items have "user-entered" urls. URLs entered in the PinSiteDialog are wrapped in 1.178 + // a special URI until we can get a valid URL. If the url is a user-entered url, decode the URL before loading it. 1.179 + Tabs.getInstance().loadUrl(decodeUserEnteredUrl(url), flags); 1.180 + Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); 1.181 + return true; 1.182 + } 1.183 + 1.184 + if (itemId == R.id.home_edit_bookmark) { 1.185 + // UI Dialog associates to the activity context, not the applications'. 1.186 + new EditBookmarkDialog(context).show(info.url); 1.187 + return true; 1.188 + } 1.189 + 1.190 + if (itemId == R.id.home_open_in_reader) { 1.191 + final String url = ReaderModeUtils.getAboutReaderForUrl(info.url); 1.192 + Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NONE); 1.193 + return true; 1.194 + } 1.195 + 1.196 + if (itemId == R.id.home_remove) { 1.197 + // Prioritize removing a history entry over a bookmark in the case of a combined item. 1.198 + if (info.hasHistoryId()) { 1.199 + new RemoveHistoryTask(context, info.historyId).execute(); 1.200 + return true; 1.201 + } 1.202 + 1.203 + if (info.hasBookmarkId()) { 1.204 + new RemoveBookmarkTask(context, info.bookmarkId).execute(); 1.205 + return true; 1.206 + } 1.207 + 1.208 + if (info.isInReadingList()) { 1.209 + (new RemoveReadingListItemTask(context, info.readingListItemId, info.url)).execute(); 1.210 + return true; 1.211 + } 1.212 + } 1.213 + 1.214 + return false; 1.215 + } 1.216 + 1.217 + @Override 1.218 + public void setUserVisibleHint (boolean isVisibleToUser) { 1.219 + if (isVisibleToUser == getUserVisibleHint()) { 1.220 + return; 1.221 + } 1.222 + 1.223 + super.setUserVisibleHint(isVisibleToUser); 1.224 + loadIfVisible(); 1.225 + } 1.226 + 1.227 + @Override 1.228 + public void onConfigurationChanged(Configuration newConfig) { 1.229 + super.onConfigurationChanged(newConfig); 1.230 + } 1.231 + 1.232 + void setCanLoadHint(boolean canLoadHint) { 1.233 + if (mCanLoadHint == canLoadHint) { 1.234 + return; 1.235 + } 1.236 + 1.237 + mCanLoadHint = canLoadHint; 1.238 + loadIfVisible(); 1.239 + } 1.240 + 1.241 + boolean getCanLoadHint() { 1.242 + return mCanLoadHint; 1.243 + } 1.244 + 1.245 + /** 1.246 + * Given a url with a user-entered scheme, extract the 1.247 + * scheme-specific component. For e.g, given "user-entered://www.google.com", 1.248 + * this method returns "//www.google.com". If the passed url 1.249 + * does not have a user-entered scheme, the same url will be returned. 1.250 + * 1.251 + * @param url to be decoded 1.252 + * @return url component entered by user 1.253 + */ 1.254 + public static String decodeUserEnteredUrl(String url) { 1.255 + Uri uri = Uri.parse(url); 1.256 + if ("user-entered".equals(uri.getScheme())) { 1.257 + return uri.getSchemeSpecificPart(); 1.258 + } 1.259 + return url; 1.260 + } 1.261 + 1.262 + protected abstract void load(); 1.263 + 1.264 + protected boolean canLoad() { 1.265 + return (mCanLoadHint && isVisible() && getUserVisibleHint()); 1.266 + } 1.267 + 1.268 + protected void loadIfVisible() { 1.269 + if (!canLoad() || mIsLoaded) { 1.270 + return; 1.271 + } 1.272 + 1.273 + load(); 1.274 + mIsLoaded = true; 1.275 + } 1.276 + 1.277 + private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> { 1.278 + private final Context mContext; 1.279 + private final int mId; 1.280 + 1.281 + public RemoveBookmarkTask(Context context, int id) { 1.282 + super(ThreadUtils.getBackgroundHandler()); 1.283 + 1.284 + mContext = context; 1.285 + mId = id; 1.286 + } 1.287 + 1.288 + @Override 1.289 + public Void doInBackground(Void... params) { 1.290 + ContentResolver cr = mContext.getContentResolver(); 1.291 + BrowserDB.removeBookmark(cr, mId); 1.292 + return null; 1.293 + } 1.294 + 1.295 + @Override 1.296 + public void onPostExecute(Void result) { 1.297 + Toast.makeText(mContext, R.string.bookmark_removed, Toast.LENGTH_SHORT).show(); 1.298 + } 1.299 + } 1.300 + 1.301 + 1.302 + private static class RemoveReadingListItemTask extends UiAsyncTask<Void, Void, Void> { 1.303 + private final int mId; 1.304 + private final String mUrl; 1.305 + private final Context mContext; 1.306 + 1.307 + public RemoveReadingListItemTask(Context context, int id, String url) { 1.308 + super(ThreadUtils.getBackgroundHandler()); 1.309 + mId = id; 1.310 + mUrl = url; 1.311 + mContext = context; 1.312 + } 1.313 + 1.314 + @Override 1.315 + public Void doInBackground(Void... params) { 1.316 + ContentResolver cr = mContext.getContentResolver(); 1.317 + BrowserDB.removeReadingListItem(cr, mId); 1.318 + 1.319 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl); 1.320 + GeckoAppShell.sendEventToGecko(e); 1.321 + 1.322 + return null; 1.323 + } 1.324 + } 1.325 + 1.326 + private static class RemoveHistoryTask extends UiAsyncTask<Void, Void, Void> { 1.327 + private final Context mContext; 1.328 + private final int mId; 1.329 + 1.330 + public RemoveHistoryTask(Context context, int id) { 1.331 + super(ThreadUtils.getBackgroundHandler()); 1.332 + 1.333 + mContext = context; 1.334 + mId = id; 1.335 + } 1.336 + 1.337 + @Override 1.338 + public Void doInBackground(Void... params) { 1.339 + BrowserDB.removeHistoryEntry(mContext.getContentResolver(), mId); 1.340 + return null; 1.341 + } 1.342 + 1.343 + @Override 1.344 + public void onPostExecute(Void result) { 1.345 + Toast.makeText(mContext, R.string.history_removed, Toast.LENGTH_SHORT).show(); 1.346 + } 1.347 + } 1.348 +}