com.frostwire.android.gui.activities.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.frostwire.android.gui.activities.MainActivity.java

Source

/*
 * Created by Angel Leon (@gubatron), Alden Torres (aldenml),
 *            Marcelina Knitter (@marcelinkaaa)
 * Copyright (c) 2011-2018, FrostWire(R). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.frostwire.android.gui.activities;

import android.app.ActionBar;
import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.Toolbar;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;

import com.andrew.apollo.IApolloService;
import com.andrew.apollo.utils.MusicUtils;
import com.andrew.apollo.utils.MusicUtils.ServiceToken;
import com.frostwire.android.AndroidPlatform;
import com.frostwire.android.R;
import com.frostwire.android.StoragePicker;
import com.frostwire.android.core.ConfigurationManager;
import com.frostwire.android.core.Constants;
import com.frostwire.android.gui.LocalSearchEngine;
import com.frostwire.android.gui.NetworkManager;
import com.frostwire.android.gui.SoftwareUpdater;
import com.frostwire.android.gui.activities.internal.MainController;
import com.frostwire.android.gui.activities.internal.NavigationMenu;
import com.frostwire.android.gui.dialogs.HandpickedTorrentDownloadDialogOnFetch;
import com.frostwire.android.gui.dialogs.NewTransferDialog;
import com.frostwire.android.gui.dialogs.SDPermissionDialog;
import com.frostwire.android.gui.dialogs.YesNoDialog;
import com.frostwire.android.gui.fragments.MainFragment;
import com.frostwire.android.gui.fragments.MyFilesFragment;
import com.frostwire.android.gui.fragments.SearchFragment;
import com.frostwire.android.gui.fragments.TransfersFragment;
import com.frostwire.android.gui.fragments.TransfersFragment.TransferStatus;
import com.frostwire.android.gui.services.Engine;
import com.frostwire.android.gui.transfers.TransferManager;
import com.frostwire.android.gui.util.DangerousPermissionsChecker;
import com.frostwire.android.gui.util.UIUtils;
import com.frostwire.android.gui.views.AbstractActivity;
import com.frostwire.android.gui.views.AbstractDialog.OnDialogClickListener;
import com.frostwire.android.gui.views.MiniPlayerView;
import com.frostwire.android.gui.views.TimerService;
import com.frostwire.android.gui.views.TimerSubscription;
import com.frostwire.android.offers.Offers;
import com.frostwire.platform.Platforms;
import com.frostwire.util.Logger;
import com.frostwire.util.Ref;
import com.frostwire.util.StringUtils;
import com.frostwire.uxstats.UXAction;
import com.frostwire.uxstats.UXStats;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Stack;

import static com.andrew.apollo.utils.MusicUtils.musicPlaybackService;
import static com.frostwire.android.util.Asyncs.async;

/**
 * @author gubatron
 * @author aldenml
 */
public class MainActivity extends AbstractActivity
        implements OnDialogClickListener, ServiceConnection, ActivityCompat.OnRequestPermissionsResultCallback {

    private static final Logger LOG = Logger.getLogger(MainActivity.class);
    public static final int PROMO_VIDEO_PREVIEW_RESULT_CODE = 100;
    private static final String FRAGMENTS_STACK_KEY = "fragments_stack";
    private static final String CURRENT_FRAGMENT_KEY = "current_fragment";
    private static final String LAST_BACK_DIALOG_ID = "last_back_dialog";
    private static final String SHUTDOWN_DIALOG_ID = "shutdown_dialog";
    private static boolean firstTime = true;
    private boolean externalStoragePermissionsRequested = false;

    private final SparseArray<DangerousPermissionsChecker> permissionsCheckers;
    private final Stack<Integer> fragmentsStack;
    private final MainController controller;
    private ServiceToken mToken;
    private NavigationMenu navigationMenu;
    private Fragment currentFragment;
    private SearchFragment search;
    private MyFilesFragment library;
    private TransfersFragment transfers;
    private BroadcastReceiver mainBroadcastReceiver;
    private final LocalBroadcastReceiver localBroadcastReceiver;
    private TimerSubscription playerSubscription;

    private boolean shuttingdown = false;

    public MainActivity() {
        super(R.layout.activity_main);
        controller = new MainController(this);
        fragmentsStack = new Stack<>();
        permissionsCheckers = initPermissionsCheckers();
        localBroadcastReceiver = new LocalBroadcastReceiver();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
            if (!(getCurrentFragment() instanceof SearchFragment)) {
                controller.switchFragment(R.id.menu_main_search);
            }
        } else if (keyCode == KeyEvent.KEYCODE_MENU) {
            toggleDrawer();
        } else {
            try {
                return super.onKeyDown(keyCode, event);
            } catch (NullPointerException npe) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void onBackPressed() {
        boolean lastBackDialogShown = false;
        if (navigationMenu.isOpen()) {
            navigationMenu.hide();
        } else if (fragmentsStack.size() > 1) {
            try {
                fragmentsStack.pop();
                int id = fragmentsStack.peek();
                Fragment fragment = getFragmentManager().findFragmentById(id);
                switchContent(fragment, false);
            } catch (Throwable e) {
                // don't break the app
                showLastBackDialog();
                lastBackDialogShown = true;
            }
        } else {
            showLastBackDialog();
            lastBackDialogShown = true;
        }
        syncNavigationMenu();
        updateHeader(getCurrentFragment());
        if (!lastBackDialogShown) {
            Offers.showInterstitialOfferIfNecessary(this, Offers.PLACEMENT_INTERSTITIAL_MAIN, false, true, true);
        }
    }

    public void shutdown() {
        if (shuttingdown) {
            // NOTE: the actual solution should be for a re-architecture for
            // a guarantee of a single call of this logic.
            // For now, just mitigate the double call if coming from the exit
            // and at the same time the close of the interstitial
            return;
        }
        shuttingdown = true;
        LocalSearchEngine.instance().cancelSearch();
        //UXStats.instance().flush(true); // sends data and ends 3rd party APIs sessions.
        finish();
        Engine.instance().shutdown();
        MusicUtils.requestMusicPlaybackServiceShutdown(this);
    }

    @Override
    public void finish() {
        if (Build.VERSION.SDK_INT >= 21) {
            finishAndRemoveTaskViaReflection();
        } else {
            super.finish();
        }
    }

    private void finishAndRemoveTaskViaReflection() {
        final Class<? extends MainActivity> clazz = getClass();
        try {
            final Method finishAndRemoveTaskMethod = clazz.getMethod("finishAndRemoveTask");
            if (finishAndRemoveTaskMethod != null) {
                finishAndRemoveTaskMethod.invoke(this);
            }
        } catch (Throwable e) {
            e.printStackTrace();
            super.finish();
        }
    }

    private boolean isShutdown() {
        return isShutdown(null);
    }

    private boolean isShutdown(Intent intent) {
        if (intent == null) {
            intent = getIntent();
        }
        boolean result = intent != null && intent.getBooleanExtra("shutdown-frostwire", false);
        if (result) {
            shutdown();
        }
        return result;
    }

    private boolean isGoHome(Intent intent) {
        if (intent == null) {
            intent = getIntent();
        }
        return intent != null && intent.getBooleanExtra("gohome-frostwire", false);
    }

    @Override
    protected void initComponents(Bundle savedInstanceState) {
        if (isShutdown()) {
            return;
        }
        updateNavigationMenu();
        setupFragments();
        setupInitialFragment(savedInstanceState);
        playerSubscription = TimerService
                .subscribe(((MiniPlayerView) findView(R.id.activity_main_player_notifier)).getRefresher(), 1);
        onNewIntent(getIntent());
        setupActionBar();
    }

    public void updateNavigationMenu(boolean updateAvailable) {
        LOG.info("updateNavigationMenu(" + updateAvailable + ")");
        if (navigationMenu == null) {
            setupDrawer();
        }
        if (updateAvailable) {
            // make sure it will remember this, even if the menu gets destroyed
            getIntent().putExtra("updateAvailable", true);
            navigationMenu.onUpdateAvailable();
        }
    }

    private void updateNavigationMenu() {
        Intent intent = getIntent();
        if (intent != null) {
            updateNavigationMenu(intent.getBooleanExtra("updateAvailable", false));
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        if (intent == null || isShutdown(intent)) {
            return;
        }
        if (isGoHome(intent)) {
            finish();
            return;
        }
        String action = intent.getAction();
        if (action != null) {
            switch (action) {
            case Constants.ACTION_SHOW_TRANSFERS:
                intent.setAction(null);
                controller.showTransfers(TransferStatus.ALL);
                break;
            case Intent.ACTION_VIEW:
                openTorrentUrl(intent);
                break;
            case Constants.ACTION_START_TRANSFER_FROM_PREVIEW:
                if (Ref.alive(NewTransferDialog.srRef)) {
                    SearchFragment.startDownload(this, NewTransferDialog.srRef.get(),
                            getString(R.string.download_added_to_queue));
                    UXStats.instance().log(UXAction.DOWNLOAD_CLOUD_FILE_FROM_PREVIEW);
                }
                break;
            case Constants.ACTION_REQUEST_SHUTDOWN:
                UXStats.instance().log(UXAction.MISC_NOTIFICATION_EXIT);
                showShutdownDialog();
                break;
            }
        }
        if (intent.hasExtra(Constants.EXTRA_DOWNLOAD_COMPLETE_NOTIFICATION)) {
            async(this, MainActivity::onDownloadCompleteNotification, intent);
        }
        if (intent.hasExtra(Constants.EXTRA_FINISH_MAIN_ACTIVITY)) {
            finish();
        }
    }

    private void openTorrentUrl(Intent intent) {
        try {
            //Open a Torrent from a URL or from a local file :), say from Astro File Manager.
            //Show me the transfer tab
            Intent i = new Intent(this, MainActivity.class);
            i.setAction(Constants.ACTION_SHOW_TRANSFERS);
            i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
            startActivity(i);
            //go!
            final String uri = intent.getDataString();
            intent.setAction(null);
            if (uri != null) {
                if (uri.startsWith("file") || uri.startsWith("http") || uri.startsWith("https")
                        || uri.startsWith("magnet")) {
                    TransferManager.instance().downloadTorrent(uri,
                            new HandpickedTorrentDownloadDialogOnFetch(this));
                } else if (uri.startsWith("content")) {
                    String newUri = saveViewContent(this, Uri.parse(uri), "content-intent.torrent");
                    if (newUri != null) {
                        TransferManager.instance().downloadTorrent(newUri,
                                new HandpickedTorrentDownloadDialogOnFetch(this));
                    }
                }
            } else {
                LOG.warn(
                        "MainActivity.onNewIntent(): Couldn't start torrent download from Intent's URI, intent.getDataString() -> null");
                LOG.warn("(maybe URI is coming in another property of the intent object - #fragmentation)");
            }
        } catch (Throwable e) {
            LOG.error("Error opening torrent from intent", e);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        localBroadcastReceiver.register(this);
        setupDrawer();
        ConfigurationManager CM = ConfigurationManager.instance();
        if (CM.getBoolean(Constants.PREF_KEY_GUI_INITIAL_SETTINGS_COMPLETE)) {
            mainResume();
            Offers.initAdNetworks(this);
        } else if (!isShutdown()) {
            controller.startWizardActivity();
        }
        checkLastSeenVersionBuild();
        registerMainBroadcastReceiver();
        syncNavigationMenu();
        updateNavigationMenu();
        //uncomment to test social links dialog
        //UIUtils.showSocialLinksDialog(this, true, null, "");
        if (CM.getBoolean(Constants.PREF_KEY_GUI_TOS_ACCEPTED)) {
            checkExternalStoragePermissionsOrBindMusicService();
        }
        async(NetworkManager.instance(), NetworkManager::queryNetworkStatusBackground);
    }

    @Override
    protected void onPause() {
        super.onPause();
        localBroadcastReceiver.unregister(this);
        if (mainBroadcastReceiver != null) {
            try {
                unregisterReceiver(mainBroadcastReceiver);
            } catch (Throwable ignored) {
                //oh well (the api doesn't provide a way to know if it's been registered before,
                //seems like overkill keeping track of these ourselves.)
            }
        }
    }

    private SparseArray<DangerousPermissionsChecker> initPermissionsCheckers() {
        SparseArray<DangerousPermissionsChecker> checkers = new SparseArray<>();
        // EXTERNAL STORAGE ACCESS CHECKER.
        final DangerousPermissionsChecker externalStorageChecker = new DangerousPermissionsChecker(this,
                DangerousPermissionsChecker.EXTERNAL_STORAGE_PERMISSIONS_REQUEST_CODE);
        //externalStorageChecker.setPermissionsGrantedCallback(() -> {});
        checkers.put(DangerousPermissionsChecker.EXTERNAL_STORAGE_PERMISSIONS_REQUEST_CODE, externalStorageChecker);
        // COARSE
        final DangerousPermissionsChecker accessCoarseLocationChecker = new DangerousPermissionsChecker(this,
                DangerousPermissionsChecker.ACCESS_COARSE_LOCATION_PERMISSIONS_REQUEST_CODE);
        checkers.put(DangerousPermissionsChecker.ACCESS_COARSE_LOCATION_PERMISSIONS_REQUEST_CODE,
                accessCoarseLocationChecker);
        // add more permissions checkers if needed...
        return checkers;
    }

    private void registerMainBroadcastReceiver() {
        mainBroadcastReceiver = new MainBroadcastReceiver(this);
        IntentFilter bf = new IntentFilter(Constants.ACTION_NOTIFY_SDCARD_MOUNTED);
        registerReceiver(mainBroadcastReceiver, bf);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (outState != null) {
            // MIGHT DO: save checkedNavViewMenuItemId in bundle.
            outState.putBoolean("updateAvailable", getIntent().getBooleanExtra("updateAvailable", false));
            super.onSaveInstanceState(outState);
            saveLastFragment(outState);
            saveFragmentsStack(outState);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.Theme_FrostWire);
        super.onCreate(savedInstanceState);
        if (!ConfigurationManager.instance().getBoolean(Constants.PREF_KEY_GUI_TOS_ACCEPTED)) {
            // we are still in the wizard.
            return;
        }
        if (isShutdown()) {
            return;
        }
        checkExternalStoragePermissionsOrBindMusicService();
        checkAccessCoarseLocationPermissions();
    }

    private void checkAccessCoarseLocationPermissions() {
        DangerousPermissionsChecker checker = permissionsCheckers
                .get(DangerousPermissionsChecker.ACCESS_COARSE_LOCATION_PERMISSIONS_REQUEST_CODE);
        if (checker != null && !checker.hasAskedBefore()) {
            checker.requestPermissions();
            ConfigurationManager.instance().setBoolean(Constants.ASKED_FOR_ACCESS_COARSE_LOCATION_PERMISSIONS,
                    true);
        } else {
            LOG.info("Asked for ACCESS_COARSE_LOCATION before, skipping.");
        }
    }

    private void checkExternalStoragePermissionsOrBindMusicService() {
        DangerousPermissionsChecker checker = permissionsCheckers
                .get(DangerousPermissionsChecker.EXTERNAL_STORAGE_PERMISSIONS_REQUEST_CODE);
        if (!externalStoragePermissionsRequested && checker != null && checker.noAccess()) {
            checker.requestPermissions();
            externalStoragePermissionsRequested = true;
        } else if (mToken == null && checker != null && !checker.noAccess()) {
            mToken = MusicUtils.bindToService(this, this);
        }
    }

    private void onNotifySdCardMounted() {
        transfers.initStorageRelatedRichNotifications(null);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (search != null) {
            // this is necessary because the Fragment#onDestroy is not
            // necessary called right in the Activity#onDestroy call, making
            // the internal mopub view possible to outlive the activity
            // destruction, creating a context leak
            search.destroyHeaderBanner();
            // TODO: make a unique call for these destroys
            search.destroyPromotionsBanner();
        }
        if (playerSubscription != null) {
            playerSubscription.unsubscribe();
        }
        if (mToken != null) {
            MusicUtils.unbindFromService(mToken);
            mToken = null;
        }
        // necessary unregisters broadcast its internal receivers, avoids leaks.
        Offers.destroyMopubInterstitials();
    }

    private void saveLastFragment(Bundle outState) {
        Fragment fragment = getCurrentFragment();
        if (fragment != null) {
            getFragmentManager().putFragment(outState, CURRENT_FRAGMENT_KEY, fragment);
        }
    }

    private void mainResume() {
        async(this, MainActivity::checkSDPermission, MainActivity::checkSDPermissionPost);
        syncNavigationMenu();
        if (firstTime) {
            if (ConfigurationManager.instance().getBoolean(Constants.PREF_KEY_NETWORK_BITTORRENT_ON_VPN_ONLY)
                    && !NetworkManager.instance().isTunnelUp()) {
                UIUtils.showDismissableMessage(findView(R.id.activity_main_parent_layout),
                        R.string.cannot_start_engine_without_vpn);
            } else {
                firstTime = false;
                Engine.instance().startServices(); // it's necessary for the first time after wizard
            }
        }
        if (Engine.instance().wasShutdown()) {
            Engine.instance().startServices();
        }
        SoftwareUpdater.getInstance().checkForUpdate(this);
    }

    private void handleSDPermissionDialogClick(int which) {
        if (which == Dialog.BUTTON_POSITIVE) {
            StoragePicker.show(this);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == StoragePicker.SELECT_FOLDER_REQUEST_CODE) {
            StoragePicker.handle(this, requestCode, resultCode, data);
        } else if (requestCode == MainActivity.PROMO_VIDEO_PREVIEW_RESULT_CODE) {
            Offers.showInterstitialOfferIfNecessary(this, Offers.PLACEMENT_INTERSTITIAL_MAIN, false, false, true);
        }
        if (!DangerousPermissionsChecker.handleOnWriteSettingsActivityResult(this)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    private void checkLastSeenVersionBuild() {
        final ConfigurationManager CM = ConfigurationManager.instance();
        final String lastSeenVersionBuild = CM.getString(Constants.PREF_KEY_CORE_LAST_SEEN_VERSION_BUILD);
        final String currentVersionBuild = Constants.FROSTWIRE_VERSION_STRING + "." + Constants.FROSTWIRE_BUILD;
        if (StringUtils.isNullOrEmpty(lastSeenVersionBuild)) {
            //fresh install
            //Offers.forceDisabledAds(this); // no ads on first session ever
            CM.setString(Constants.PREF_KEY_CORE_LAST_SEEN_VERSION_BUILD, currentVersionBuild);
            UXStats.instance().log(UXAction.CONFIGURATION_WIZARD_FIRST_TIME);
        } else if (!currentVersionBuild.equals(lastSeenVersionBuild)) {
            //just updated.
            //Offers.forceDisabledAds(this); // no ads right after update
            CM.setString(Constants.PREF_KEY_CORE_LAST_SEEN_VERSION_BUILD, currentVersionBuild);
            UXStats.instance().log(UXAction.CONFIGURATION_WIZARD_AFTER_UPDATE);
        }
    }

    private void toggleDrawer() {
        if (navigationMenu.isOpen()) {
            navigationMenu.hide();
        } else {
            navigationMenu.show();
            syncNavigationMenu();
        }
        updateHeader(getCurrentFragment());
    }

    private void showLastBackDialog() {
        YesNoDialog dlg = YesNoDialog.newInstance(LAST_BACK_DIALOG_ID, R.string.minimize_frostwire,
                R.string.are_you_sure_you_wanna_leave, YesNoDialog.FLAG_DISMISS_ON_OK_BEFORE_PERFORM_DIALOG_CLICK);
        dlg.show(getFragmentManager()); //see onDialogClick
    }

    public void showShutdownDialog() {
        UXStats.instance().flush();
        YesNoDialog dlg = YesNoDialog.newInstance(SHUTDOWN_DIALOG_ID, R.string.app_shutdown_dlg_title,
                R.string.app_shutdown_dlg_message, YesNoDialog.FLAG_DISMISS_ON_OK_BEFORE_PERFORM_DIALOG_CLICK);
        dlg.show(getFragmentManager()); //see onDialogClick
    }

    public void onDialogClick(String tag, int which) {
        if (tag.equals(LAST_BACK_DIALOG_ID) && which == Dialog.BUTTON_POSITIVE) {
            onLastDialogButtonPositive();
        } else if (tag.equals(SHUTDOWN_DIALOG_ID) && which == Dialog.BUTTON_POSITIVE) {
            onShutdownDialogButtonPositive();
        } else if (tag.equals(SDPermissionDialog.TAG)) {
            handleSDPermissionDialogClick(which);
        }
    }

    private void onLastDialogButtonPositive() {
        finish();
    }

    private void onShutdownDialogButtonPositive() {
        shutdown();
    }

    public void syncNavigationMenu() {
        invalidateOptionsMenu();
        navigationMenu.updateCheckedItem(getNavMenuIdByFragment(getCurrentFragment()));
    }

    private void setupFragments() {
        search = (SearchFragment) getFragmentManager().findFragmentById(R.id.activity_main_fragment_search);
        search.connectDrawerLayoutFilterView(findView(R.id.activity_main_drawer_layout),
                findView(R.id.activity_main_keyword_filter_drawer_view));
        library = (MyFilesFragment) getFragmentManager().findFragmentById(R.id.activity_main_fragment_my_files);
        transfers = (TransfersFragment) getFragmentManager()
                .findFragmentById(R.id.activity_main_fragment_transfers);
    }

    private void hideFragments() {
        try {
            getFragmentManager().executePendingTransactions();
        } catch (Throwable t) {
            LOG.warn(t.getMessage(), t);
        }
        FragmentTransaction tx = getFragmentManager().beginTransaction();
        tx.hide(search).hide(library).hide(transfers);
        try {
            tx.commit();
        } catch (IllegalStateException e) {
            // if not that we can do a lot here, since the root of the problem
            // is the multiple entry points to MainActivity, just let it run
            // a possible inconsistent (but probably right) version.
            // in the future with a higher API, commitNow should be considered
            LOG.warn("Error running commit in fragment transaction, using weaker option", e);
            try {
                tx.commitAllowingStateLoss();
            } catch (IllegalStateException e2) {
                // \_()_/
                LOG.warn(
                        "Error running commit in fragment transaction, weaker option also failed (commit already called - mCommited=true)",
                        e2);
            }
        }
    }

    private void setupInitialFragment(Bundle savedInstanceState) {
        Fragment fragment = null;
        if (savedInstanceState != null) {
            fragment = getFragmentManager().getFragment(savedInstanceState, CURRENT_FRAGMENT_KEY);
            restoreFragmentsStack(savedInstanceState);
        }
        if (fragment == null) {
            fragment = search;
            setCheckedItem(R.id.menu_main_search);
        }
        switchContent(fragment);
    }

    private void setCheckedItem(int navMenuItemId) {
        if (navigationMenu != null) {
            navigationMenu.updateCheckedItem(navMenuItemId);
        }
    }

    private void saveFragmentsStack(Bundle outState) {
        int[] stack = new int[fragmentsStack.size()];
        for (int i = 0; i < stack.length; i++) {
            stack[i] = fragmentsStack.get(i);
        }
        outState.putIntArray(FRAGMENTS_STACK_KEY, stack);
    }

    private void restoreFragmentsStack(Bundle savedInstanceState) {
        try {
            int[] stack = savedInstanceState.getIntArray(FRAGMENTS_STACK_KEY);
            if (stack != null) {
                for (int id : stack) {
                    fragmentsStack.push(id);
                }
            }
        } catch (Throwable ignored) {
        }
    }

    private void updateHeader(Fragment fragment) {
        try {
            Toolbar toolbar = findToolbar();
            if (toolbar == null) {
                LOG.warn("updateHeader(): Check your logic, no actionBar available");
                return;
            }
            if (fragment instanceof MainFragment) {
                View header = ((MainFragment) fragment).getHeader(this);
                if (header != null) {
                    setToolbarView(header);
                }
            }
            if (navigationMenu != null) {
                MenuItem item = navigationMenu.getCheckedItem();
                setTitle(item.getTitle());
            }
        } catch (Throwable e) {
            LOG.error("Error updating main header", e);
        }
    }

    private void switchContent(Fragment fragment, boolean addToStack) {
        hideFragments();
        FragmentTransaction transaction = getFragmentManager().beginTransaction().show(fragment);
        try {
            transaction.commitAllowingStateLoss();
        } catch (Throwable ignored) {
        }
        if (addToStack && (fragmentsStack.isEmpty() || fragmentsStack.peek() != fragment.getId())) {
            fragmentsStack.push(fragment.getId());
        }
        currentFragment = fragment;
        updateHeader(fragment);
        if (currentFragment instanceof MainFragment) {
            ((MainFragment) currentFragment).onShow();
        }
    }

    /*
     * The following methods are only public to be able to use them from another package(internal).
     */

    public Fragment getFragmentByNavMenuId(int id) {
        switch (id) {
        case R.id.menu_main_search:
            return search;
        case R.id.menu_main_library:
            return library;
        case R.id.menu_main_transfers:
            return transfers;
        default:
            return null;
        }
    }

    private int getNavMenuIdByFragment(Fragment fragment) {
        int menuId = -1;
        if (fragment == search) {
            menuId = R.id.menu_main_search;
        } else if (fragment == library) {
            menuId = R.id.menu_main_library;
        } else if (fragment == transfers) {
            menuId = R.id.menu_main_transfers;
        }
        return menuId;
    }

    public void switchContent(Fragment fragment) {
        switchContent(fragment, true);
    }

    public Fragment getCurrentFragment() {
        return currentFragment;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (navigationMenu != null) {
            try {
                navigationMenu.onOptionsItemSelected(item);
            } catch (Throwable t) {
                // usually java.lang.IllegalArgumentException: No drawer view found with gravity LEFT
                return false;
            }
            return false;
        }
        if (item == null) {
            return false;
        }
        switch (item.getItemId()) {
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        navigationMenu.onConfigurationChanged(newConfig);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        navigationMenu.syncState();
    }

    private void setupActionBar() {
        ActionBar bar = getActionBar();
        if (bar != null) {
            bar.setCustomView(R.layout.view_custom_actionbar);
            bar.setDisplayShowCustomEnabled(true);
            bar.setDisplayHomeAsUpEnabled(true);
            bar.setHomeButtonEnabled(true);
        }
    }

    private void setupDrawer() {
        DrawerLayout drawerLayout = findView(R.id.activity_main_drawer_layout);
        Toolbar toolbar = findToolbar();
        navigationMenu = new NavigationMenu(controller, drawerLayout, toolbar);
    }

    public void onServiceConnected(final ComponentName name, final IBinder service) {
        musicPlaybackService = IApolloService.Stub.asInterface(service);
    }

    public void onServiceDisconnected(final ComponentName name) {
        musicPlaybackService = null;
    }

    //@Override commented override since we are in API 16, but it will in API 23
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        DangerousPermissionsChecker checker = permissionsCheckers.get(requestCode);
        if (checker != null) {
            checker.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
        Offers.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    public void performYTSearch(String ytUrl) {
        SearchFragment searchFragment = (SearchFragment) getFragmentByNavMenuId(R.id.menu_main_search);
        searchFragment.performYTSearch(ytUrl);
        switchContent(searchFragment);
    }

    public static void refreshTransfers(Context context) {
        Intent intent = new Intent(context, MainActivity.class).setAction(Constants.ACTION_SHOW_TRANSFERS)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        try {
            context.startActivity(intent);
        } catch (Throwable t) {
            LOG.error(t.getMessage(), t);
        }
    }

    private void onDownloadCompleteNotification(Intent intent) {
        controller.showTransfers(TransferStatus.COMPLETED);
        TransferManager.instance().clearDownloadsToReview();
        try {
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (notificationManager != null) {
                notificationManager.cancel(Constants.NOTIFICATION_DOWNLOAD_TRANSFER_FINISHED);
            }
            Bundle extras = intent.getExtras();
            if (extras != null) {
                String downloadCompletePath = extras.getString(Constants.EXTRA_DOWNLOAD_COMPLETE_PATH);
                if (downloadCompletePath != null) {
                    File file = new File(downloadCompletePath);
                    if (file.isFile()) {
                        UIUtils.openFile(this, file.getAbsoluteFile());
                    }
                }
            }
        } catch (Throwable e) {
            LOG.warn("Error handling download complete notification", e);
        }
    }

    /**
     * @return true if the SD Permission dialog must be shown
     */
    private boolean checkSDPermission() {
        if (!AndroidPlatform.saf()) {
            return false;
        }
        try {
            File data = Platforms.data();
            File parent = data.getParentFile();
            return AndroidPlatform.saf(parent)
                    && (!Platforms.fileSystem().canWrite(parent) && !SDPermissionDialog.visible);
        } catch (Throwable e) {
            // we can't do anything about this
            LOG.error("Unable to detect if we have SD permissions", e);
            return false;
        }
    }

    private void checkSDPermissionPost(boolean showPermissionDialog) {
        if (showPermissionDialog) {
            SDPermissionDialog dlg = SDPermissionDialog.newInstance();
            FragmentManager fragmentManager = getFragmentManager();
            try {
                if (fragmentManager != null) {
                    dlg.show(fragmentManager);
                }
            } catch (IllegalStateException ignored) {
            }
        }
    }

    // TODO: refactor and move this method for a common place when needed
    private static String saveViewContent(Context context, Uri uri, String name) {
        InputStream inStream = null;
        OutputStream outStream = null;
        if (!Platforms.temp().exists()) {
            boolean mkdirs = Platforms.temp().mkdirs();
            if (!mkdirs) {
                LOG.warn("saveViewContent() could not create Platforms.temp() directory.");
            }
        }
        File target = new File(Platforms.temp(), name);
        try {
            inStream = context.getContentResolver().openInputStream(uri);
            outStream = new FileOutputStream(target);
            byte[] buffer = new byte[16384]; // MAGIC_NUMBER
            int bytesRead;
            if (inStream != null) {
                while ((bytesRead = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (Throwable e) {
            LOG.error("Error when copying file from " + uri + " to temp/" + name, e);
            return null;
        } finally {
            IOUtils.closeQuietly(inStream);
            IOUtils.closeQuietly(outStream);
        }
        return "file://" + target.getAbsolutePath();
    }

    private static final class MainBroadcastReceiver extends BroadcastReceiver {
        private final WeakReference<MainActivity> activityRef;

        MainBroadcastReceiver(MainActivity activity) {
            activityRef = Ref.weak(activity);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (Ref.alive(activityRef) && Constants.ACTION_NOTIFY_SDCARD_MOUNTED.equals(intent.getAction())) {
                activityRef.get().onNotifySdCardMounted();
            }
        }
    }

    private final class LocalBroadcastReceiver extends BroadcastReceiver {

        private final IntentFilter intentFilter;

        LocalBroadcastReceiver() {
            intentFilter = new IntentFilter();
            intentFilter.addAction(Constants.ACTION_NOTIFY_UPDATE_AVAILABLE);
            intentFilter.addAction(Constants.ACTION_NOTIFY_DATA_INTERNET_CONNECTION);
        }

        public void register(Context context) {
            LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter);
        }

        public void unregister(Context context) {
            LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (Constants.ACTION_NOTIFY_UPDATE_AVAILABLE.equals(action)) {
                boolean value = intent.getBooleanExtra("value", false);
                Intent mainActivityIntent = getIntent();
                if (mainActivityIntent != null) {
                    mainActivityIntent.putExtra("updateAvailable", value);
                }
                updateNavigationMenu(value);
            }
            if (Constants.ACTION_NOTIFY_DATA_INTERNET_CONNECTION.equals(action)) {
                boolean isDataUp = intent.getBooleanExtra("isDataUp", true);
                if (!isDataUp) {
                    UIUtils.showDismissableMessage(findView(android.R.id.content),
                            R.string.no_data_check_internet_connection);
                }
                search.setDataUp(isDataUp);
            }
        }
    }
}