mobile/android/base/home/HomeFragment.java

changeset 0
6474c204b198
     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 +}

mercurial