package io.lbry.browser;

import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v4.media.session.MediaSessionCompat;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.text.style.TypefaceSpan;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.Menu;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.messaging.FirebaseMessaging;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.view.ActionMode;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.media.session.MediaButtonReceiver;
import androidx.preference.PreferenceManager;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLParameters;

import io.lbry.browser.adapter.NavigationMenuAdapter;
import io.lbry.browser.adapter.NotificationListAdapter;
import io.lbry.browser.adapter.StartupStageAdapter;
import io.lbry.browser.adapter.UrlSuggestionListAdapter;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.dialog.ContentScopeDialogFragment;
import io.lbry.browser.exceptions.AuthTokenInvalidatedException;
import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.listener.CameraPermissionListener;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.listener.FetchChannelsListener;
import io.lbry.browser.listener.FetchClaimsListener;
import io.lbry.browser.listener.FilePickerListener;
import io.lbry.browser.listener.PIPModeListener;
import io.lbry.browser.listener.ScreenOrientationListener;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.listener.StoragePermissionListener;
import io.lbry.browser.listener.WalletBalanceListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.ClaimCacheKey;
import io.lbry.browser.model.NavMenuItem;
import io.lbry.browser.model.StartupStage;
import io.lbry.browser.model.Tag;
import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.WalletBalance;
import io.lbry.browser.model.WalletSync;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.model.lbryinc.Reward;
import io.lbry.browser.model.lbryinc.RewardVerified;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.GenericTaskHandler;
import io.lbry.browser.tasks.RewardVerifiedHandler;
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
import io.lbry.browser.tasks.claim.ClaimListTask;
import io.lbry.browser.tasks.lbryinc.AndroidPurchaseTask;
import io.lbry.browser.tasks.lbryinc.ClaimRewardTask;
import io.lbry.browser.tasks.lbryinc.FetchRewardsTask;
import io.lbry.browser.tasks.LighthouseAutoCompleteTask;
import io.lbry.browser.tasks.MergeSubscriptionsTask;
import io.lbry.browser.tasks.claim.ResolveTask;
import io.lbry.browser.tasks.lbryinc.NotificationDeleteTask;
import io.lbry.browser.tasks.lbryinc.NotificationListTask;
import io.lbry.browser.tasks.lbryinc.NotificationUpdateTask;
import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask;
import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler;
import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask;
import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask;
import io.lbry.browser.tasks.wallet.SyncApplyTask;
import io.lbry.browser.tasks.wallet.SyncGetTask;
import io.lbry.browser.tasks.wallet.SyncSetTask;
import io.lbry.browser.tasks.wallet.UnlockTipsTask;
import io.lbry.browser.tasks.wallet.WalletBalanceTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.ui.channel.ChannelFormFragment;
import io.lbry.browser.ui.channel.ChannelFragment;
import io.lbry.browser.ui.channel.ChannelManagerFragment;
import io.lbry.browser.ui.findcontent.EditorsChoiceFragment;
import io.lbry.browser.ui.findcontent.FileViewFragment;
import io.lbry.browser.ui.findcontent.FollowingFragment;
import io.lbry.browser.ui.library.LibraryFragment;
import io.lbry.browser.ui.other.AboutFragment;
import io.lbry.browser.ui.publish.PublishFormFragment;
import io.lbry.browser.ui.publish.PublishFragment;
import io.lbry.browser.ui.publish.PublishesFragment;
import io.lbry.browser.ui.findcontent.AllContentFragment;
import io.lbry.browser.ui.findcontent.SearchFragment;
import io.lbry.browser.ui.findcontent.ShuffleFragment;
import io.lbry.browser.ui.other.SettingsFragment;
import io.lbry.browser.ui.wallet.InvitesFragment;
import io.lbry.browser.ui.wallet.RewardsFragment;
import io.lbry.browser.ui.wallet.WalletFragment;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
import io.lbry.browser.utils.LbryAnalytics;
import io.lbry.browser.utils.LbryUri;
import io.lbry.browser.utils.Lbryio;
import io.lbry.lbrysdk.DownloadManager;
import io.lbry.lbrysdk.LbrynetService;
import io.lbry.lbrysdk.ServiceHelper;
import io.lbry.lbrysdk.Utils;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import okhttp3.OkHttpClient;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.P;

public class MainActivity extends AppCompatActivity implements SdkStatusListener,
        SharedPreferences.OnSharedPreferenceChangeListener,
        ActionMode.Callback, SelectionModeListener {
    private static final String CHANNEL_ID_PLAYBACK = "io.lbry.browser.LBRY_PLAYBACK_CHANNEL";
    private static final int PLAYBACK_NOTIFICATION_ID = 3;
    private static final String SPECIAL_URL_PREFIX = "lbry://?";
    private static final int REMOTE_NOTIFICATION_REFRESH_TTL = 300000; // 5 minutes
    public static final String SKU_SKIP = "lbryskip";

    public static final int SOURCE_NOW_PLAYING_FILE = 1;
    public static final int SOURCE_NOW_PLAYING_SHUFFLE = 2;
    public static MainActivity instance;

    private boolean shuttingDown;
    private Date remoteNotifcationsLastLoaded;
    private Map<String, Class> specialRouteFragmentClassMap;
    @Getter
    private boolean inPictureInPictureMode;
    @Getter
    private boolean inFullscreenMode;
    // make tip unlock a global operation
    @Getter
    private boolean unlockingTips;

    public static SimpleExoPlayer appPlayer;
    public static Cache playerCache;
    public static boolean playerReassigned;
    public CastContext castContext;
    public static CastPlayer castPlayer;
    public static int nowPlayingSource;
    public static Claim nowPlayingClaim;
    public static String nowPlayingClaimUrl;
    public static boolean startingFilePickerActivity = false;
    public static boolean startingShareActivity = false;
    public static boolean startingPermissionRequest = false;
    public static final boolean startingSignInFlowActivity = false;

    private ActionMode actionMode;
    private BillingClient billingClient;
    @Getter
    private boolean enteringPIPMode = false;
    private boolean fullSyncInProgress = false;
    private int queuedSyncCount = 0;
    private String cameraOutputFilename;
    private Bitmap nowPlayingClaimBitmap;

    @Setter
    private BackPressInterceptor backPressInterceptor;
    private WebSocketClient webSocketClient;

    @Getter
    private String firebaseMessagingToken;

    private NotificationListAdapter notificationListAdapter;

    private Map<String, Fragment> openNavFragments;
    private static final Map<Class, Integer> fragmentClassNavIdMap = new HashMap<>();
    static {
        Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);

        fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
        fragmentClassNavIdMap.put(EditorsChoiceFragment.class, NavMenuItem.ID_ITEM_EDITORS_CHOICE);
        fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT);
        fragmentClassNavIdMap.put(ShuffleFragment.class, NavMenuItem.ID_ITEM_SHUFFLE);

        fragmentClassNavIdMap.put(PublishFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH);
        fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS);
        fragmentClassNavIdMap.put(LibraryFragment.class, NavMenuItem.ID_ITEM_LIBRARY);
        fragmentClassNavIdMap.put(PublishesFragment.class, NavMenuItem.ID_ITEM_PUBLISHES);

        fragmentClassNavIdMap.put(WalletFragment.class, NavMenuItem.ID_ITEM_WALLET);
        fragmentClassNavIdMap.put(RewardsFragment.class, NavMenuItem.ID_ITEM_REWARDS);
        fragmentClassNavIdMap.put(InvitesFragment.class, NavMenuItem.ID_ITEM_INVITES);

        fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS);
        fragmentClassNavIdMap.put(AboutFragment.class, NavMenuItem.ID_ITEM_ABOUT);

        // Internal (sub-)pages
        fragmentClassNavIdMap.put(FileViewFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
        fragmentClassNavIdMap.put(ChannelFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
        fragmentClassNavIdMap.put(SearchFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
    }

    public static final int REQUEST_STORAGE_PERMISSION = 1001;
    public static final int REQUEST_CAMERA_PERMISSION = 1002;
    public static final int REQUEST_SIMPLE_SIGN_IN = 2001;
    public static final int REQUEST_WALLET_SYNC_SIGN_IN = 2002;
    public static final int REQUEST_REWARDS_VERIFY_SIGN_IN = 2003;

    public static final int REQUEST_FILE_PICKER = 5001;
    public static final int REQUEST_VIDEO_CAPTURE = 5002;
    public static final int REQUEST_TAKE_PHOTO = 5003;

    // broadcast action names
    public static final String ACTION_SDK_READY = "io.lbry.browser.Broadcast.SdkReady";
    public static final String ACTION_AUTH_TOKEN_GENERATED = "io.lbry.browser.Broadcast.AuthTokenGenerated";
    public static final String ACTION_USER_AUTHENTICATION_SUCCESS = "io.lbry.browser.Broadcast.UserAuthenticationSuccess";
    public static final String ACTION_USER_SIGN_IN_SUCCESS = "io.lbry.browser.Broadcast.UserSignInSuccess";
    public static final String ACTION_USER_AUTHENTICATION_FAILED = "io.lbry.browser.Broadcast.UserAuthenticationFailed";
    public static final String ACTION_NOW_PLAYING_CLAIM_UPDATED = "io.lbry.browser.Broadcast.NowPlayingClaimUpdated";
    public static final String ACTION_NOW_PLAYING_CLAIM_CLEARED = "io.lbry.browser.Broadcast.NowPlayingClaimCleared";
    public static final String ACTION_PUBLISH_SUCCESSFUL = "io.lbry.browser.Broadcast.PublishSuccessful";
    public static final String ACTION_OPEN_ALL_CONTENT_TAG = "io.lbry.browser.Broadcast.OpenAllContentTag";
    public static final String ACTION_WALLET_BALANCE_UPDATED = "io.lbry.browser.Broadcast.WalletBalanceUpdated";
    public static final String ACTION_OPEN_CHANNEL_URL = "io.lbry.browser.Broadcast.OpenChannelUrl";
    public static final String ACTION_OPEN_WALLET_PAGE = "io.lbry.browser.Broadcast.OpenWalletPage";
    public static final String ACTION_OPEN_REWARDS_PAGE = "io.lbry.browser.Broadcast.OpenRewardsPage";
    public static final String ACTION_SAVE_SHARED_USER_STATE = "io.lbry.browser.Broadcast.SaveSharedUserState";

    // preference keys
    public static final String PREFERENCE_KEY_BACKGROUND_PLAYBACK = "io.lbry.browser.preference.userinterface.BackgroundPlayback";
    public static final String PREFERENCE_KEY_MEDIA_AUTOPLAY = "io.lbry.browser.preference.userinterface.MediaAutoplay";
    public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode";
    public static final String PREFERENCE_KEY_SHOW_MATURE_CONTENT = "io.lbry.browser.preference.userinterface.ShowMatureContent";
    public static final String PREFERENCE_KEY_SHOW_URL_SUGGESTIONS = "io.lbry.browser.preference.userinterface.UrlSuggestions";
    public static final String PREFERENCE_KEY_MINI_PLAYER_BOTTOM_MARGIN = "io.lbry.browser.preference.userinterface.MiniPlayerBottomMargin";
    public static final String PREFERENCE_KEY_NOTIFICATION_COMMENTS = "io.lbry.browser.preference.notifications.Comments";
    public static final String PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS = "io.lbry.browser.preference.notifications.Subscriptions";
    public static final String PREFERENCE_KEY_NOTIFICATION_REWARDS = "io.lbry.browser.preference.notifications.Rewards";
    public static final String PREFERENCE_KEY_NOTIFICATION_CONTENT_INTERESTS = "io.lbry.browser.preference.notifications.ContentInterests";
    public static final String PREFERENCE_KEY_NOTIFICATION_CREATOR = "io.lbry.browser.preference.notifications.Creator";
    public static final String PREFERENCE_KEY_KEEP_SDK_BACKGROUND = "io.lbry.browser.preference.other.KeepSdkInBackground";
    public static final String PREFERENCE_KEY_PARTICIPATE_DATA_NETWORK = "io.lbry.browser.preference.other.ParticipateInDataNetwork";
    public static final String PREFERENCE_KEY_SEND_BUFFERING_EVENTS = "io.lbry.browser.preference.other.SendBufferingEvents";
    public static final String PREFERENCE_KEY_SHARE_USAGE_DATA = "io.lbry.browser.preference.other.ShareUsageData";

    // Internal flags / setting preferences
    public static final String PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT = "io.lbry.browser.preference.internal.WalletSkipAccount";
    public static final String PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED = "io.lbry.browser.preference.internal.WalletSyncEnabled";
    public static final String PREFERENCE_KEY_INTERNAL_WALLET_RECEIVE_ADDRESS = "io.lbry.browser.preference.internal.WalletReceiveAddress";
    public static final String PREFERENCE_KEY_INTERNAL_REWARDS_NOT_INTERESTED = "io.lbry.browser.preference.internal.RewardsNotInterested";
    public static final String PREFERENCE_KEY_INTERNAL_NEW_ANDROID_REWARD_CLAIMED = "io.lbry.browser.preference.internal.NewAndroidRewardClaimed";
    public static final String PREFERENCE_KEY_INTERNAL_INITIAL_SUBSCRIPTION_MERGE_DONE = "io.lbry.browser.preference.internal.InitialSubscriptionMergeDone";

    public static final String PREFERENCE_KEY_INTERNAL_FIRST_RUN_COMPLETED = "io.lbry.browser.preference.internal.FirstRunCompleted";
    public static final String PREFERENCE_KEY_INTERNAL_FIRST_AUTH_COMPLETED = "io.lbry.browser.preference.internal.FirstAuthCompleted";

    private final int CHECK_SDK_READY_INTERVAL = 1000;

    public static final String PREFERENCE_KEY_AUTH_TOKEN = "io.lbry.browser.Preference.AuthToken";

    public static final String SECURE_VALUE_KEY_SAVED_PASSWORD = "io.lbry.browser.PX";
    public static final String SECURE_VALUE_FIRST_RUN_PASSWORD = "firstRunPassword";

    private static final String TAG = "LbryMain";

    private NavigationMenuAdapter navMenuAdapter;
    private UrlSuggestionListAdapter urlSuggestionListAdapter;
    private List<UrlSuggestion> recentUrlHistory;
    private boolean hasLoadedFirstBalance;

    // broadcast receivers
    private BroadcastReceiver serviceActionsReceiver;
    private BroadcastReceiver requestsReceiver;

    private static boolean appStarted;
    private boolean serviceRunning;
    private CheckSdkReadyTask checkSdkReadyTask;
    private PlayerNotificationManager playerNotificationManager;
    private MediaSessionCompat mediaSession;
    private boolean receivedStopService;
    private ActionBarDrawerToggle toggle;
    private SwipeRefreshLayout notificationsSwipeContainer;
    private SyncSetTask syncSetTask = null;
    private List<WalletSync> pendingSyncSetQueue;
    @Getter
    private DatabaseHelper dbHelper;
    private int selectedMenuItemId = -1;
    private List<CameraPermissionListener> cameraPermissionListeners;
    private List<DownloadActionListener> downloadActionListeners;
    private List<FilePickerListener> filePickerListeners;
    private List<PIPModeListener> pipModeListeners;
    private List<ScreenOrientationListener> screenOrientationListeners;
    private List<SdkStatusListener> sdkStatusListeners;
    private List<StoragePermissionListener> storagePermissionListeners;
    private List<WalletBalanceListener> walletBalanceListeners;
    private List<FetchClaimsListener> fetchClaimsListeners;
    private List<FetchChannelsListener> fetchChannelsListeners;
    @Getter
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private boolean walletBalanceUpdateScheduled;
    private boolean shouldOpenUserSelectedMenuItem;
    private boolean walletSyncScheduled;
    private String pendingAllContentTag;
    private String pendingChannelUrl;
    private boolean pendingOpenWalletPage;
    private boolean pendingOpenRewardsPage;
    private boolean pendingFollowingReload;

    // startup stages (to be able to determine how far a user made it if startup fails)
    // and display a more useful message for troubleshooting
    private static final int STARTUP_STAGE_INSTALL_ID_LOADED = 1;
    private static final int STARTUP_STAGE_KNOWN_TAGS_LOADED = 2;
    private static final int STARTUP_STAGE_EXCHANGE_RATE_LOADED = 3;
    private static final int STARTUP_STAGE_USER_AUTHENTICATED = 4;
    private static final int STARTUP_STAGE_NEW_INSTALL_DONE = 5;
    private static final int STARTUP_STAGE_SUBSCRIPTIONS_LOADED = 6;
    private static final int STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED = 7;
    private static final int STARTUP_STAGE_BLOCK_LIST_LOADED = 8;
    private static final int STARTUP_STAGE_FILTER_LIST_LOADED = 9;
    private static final int DEFAULT_MINI_PLAYER_MARGIN = 4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        instance = this;
        // workaround to fix dark theme because https://issuetracker.google.com/issues/37124582
        try {
            new WebView(this);
        } catch (Exception ex) {
            // pass (don't fail initialization on some _weird_ device implementations)
        }
        AppCompatDelegate.setDefaultNightMode(isDarkMode() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);

        initKeyStore();
        loadAuthToken();

        if (Build.VERSION.SDK_INT >= M && !isDarkMode()) {
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
        initSpecialRouteMap();

        LbryAnalytics.init(this);
        try {
            FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener<String>() {
                @Override
                public void onComplete(@NonNull Task<String> task) {
                    if (!task.isSuccessful()) {
                        return;
                    }
                    firebaseMessagingToken = task.getResult();
                }
            });
        } catch (IllegalStateException ex) {
            // pass
        }

        super.onCreate(savedInstanceState);
        dbHelper = new DatabaseHelper(this);
        checkNotificationOpenIntent(getIntent());
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        updateMiniPlayerMargins();

        // setup the billing client in main activity (to handle cases where the verification purchase flow may have been interrupted)
        billingClient = BillingClient.newBuilder(this)
                .setListener(new PurchasesUpdatedListener() {
                    @Override
                    public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
                        int responseCode = billingResult.getResponseCode();
                        if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null)
                        {
                            for (Purchase purchase : purchases) {
                                handlePurchase(purchase);
                            }
                        }
                    }
                })
                .enablePendingPurchases()
                .build();
        establishBillingClientConnection();

        playerNotificationManager = new PlayerNotificationManager(
                this, LbrynetService.NOTIFICATION_CHANNEL_ID, PLAYBACK_NOTIFICATION_ID, new PlayerNotificationDescriptionAdapter());

        // TODO: Check Google Play Services availability
        // castContext = CastContext.getSharedInstance(this);

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.content_main), new OnApplyWindowInsetsListener() {
            @Override
            public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                ViewCompat.onApplyWindowInsets(findViewById(R.id.url_suggestions_container),
                        insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom()));

                return ViewCompat.onApplyWindowInsets(v,
                        insets.replaceSystemWindowInsets(
                                0,
                                0,
                                0,
                                insets.getSystemWindowInsetBottom()));
            }
        });

        // register receivers
        registerRequestsReceiver();
        registerServiceActionsReceiver();

        View decorView = getWindow().getDecorView();
        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                    // not fullscreen
                }
            }
        });

        // setup uri bar
        setupUriBar();
        initNotificationsPage();
        loadUnseenNotificationsCount();

        // other
        pendingSyncSetQueue = new ArrayList<>();
        openNavFragments = new HashMap<>();

        cameraPermissionListeners = new ArrayList<>();
        downloadActionListeners = new ArrayList<>();
        fetchChannelsListeners = new ArrayList<>();
        fetchClaimsListeners = new ArrayList<>();
        filePickerListeners = new ArrayList<>();
        pipModeListeners = new ArrayList<>();
        screenOrientationListeners = new ArrayList<>();
        sdkStatusListeners = new ArrayList<>();
        storagePermissionListeners = new ArrayList<>();
        walletBalanceListeners = new ArrayList<>();

        sdkStatusListeners.add(this);

        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.addOnBackStackChangedListener(backStackChangedListener);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                if (shouldOpenUserSelectedMenuItem) {
                    openSelectedMenuItem();
                    shouldOpenUserSelectedMenuItem = false;
                }
            }

            @Override
            public void onDrawerSlide(View drawerView, float slideOffset) {
                if (slideOffset != 0) {
                    clearWunderbarFocus(findViewById(R.id.wunderbar));
                    hideNotifications();
                }
                super.onDrawerSlide(drawerView, slideOffset);
            }
        };
        drawer.addDrawerListener(toggle);
        toggle.syncState();
        toggle.setToolbarNavigationClickListener((view) -> {
            if (toggle != null && !toggle.isDrawerIndicatorEnabled()) {
                FragmentManager manager = getSupportFragmentManager();
                if (manager != null) {
                    manager.popBackStack();
                    setSelectedNavMenuItemForFragment(getCurrentFragment());
                }
            }
        });

        findViewById(R.id.global_now_playing_close).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopExoplayer();
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                nowPlayingClaim = null;
                nowPlayingClaimUrl = null;
                nowPlayingClaimBitmap = null;
                findViewById(R.id.global_now_playing_card).setVisibility(View.GONE);
            }
        });

        findViewById(R.id.wunderbar_notifications).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                View container = findViewById(R.id.notifications_container);
                if (container.getVisibility() != View.VISIBLE) {
                    showNotifications();
                } else {
                    hideNotifications();
                }
            }
        });

        notificationsSwipeContainer = findViewById(R.id.notifications_list_swipe_container);
        notificationsSwipeContainer.setColorSchemeResources(R.color.nextLbryGreen);
        notificationsSwipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                notificationsSwipeContainer.setRefreshing(true);
                loadRemoteNotifications(false);
            }
        });

        findViewById(R.id.global_now_playing_card).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) {
                    hideNotifications();
                    if (nowPlayingSource == SOURCE_NOW_PLAYING_SHUFFLE) {
                        openFragment(ShuffleFragment.class, true, NavMenuItem.ID_ITEM_SHUFFLE);
                    } else {
                        openFileUrl(nowPlayingClaimUrl);
                    }
                }
            }
        });

        // display custom navigation menu
        LinearLayoutManager llm = new LinearLayoutManager(this);
        RecyclerView navItemsView = findViewById(R.id.nav_view_items);
        navItemsView.setLayoutManager(llm);
        navMenuAdapter = new NavigationMenuAdapter(flattenNavMenu(buildNavMenu(this)), this);
        navMenuAdapter.setListener(new NavigationMenuAdapter.NavigationMenuItemClickListener() {
            @Override
            public void onNavigationMenuItemClicked(NavMenuItem menuItem) {
                if (navMenuAdapter.getCurrentItemId() == menuItem.getId() && !Arrays.asList(
                        NavMenuItem.ID_ITEM_FOLLOWING, NavMenuItem.ID_ITEM_ALL_CONTENT, NavMenuItem.ID_ITEM_WALLET).contains(menuItem.getId())) {
                    // already open
                    navMenuAdapter.setCurrentItem(menuItem);
                    closeDrawer();
                    return;
                }

                navMenuAdapter.setCurrentItem(menuItem);
                shouldOpenUserSelectedMenuItem = true;
                selectedMenuItemId = menuItem.getId();
                closeDrawer();
            }
        });
        navItemsView.setAdapter(navMenuAdapter);

        findViewById(R.id.sign_in_button_container).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                walletSyncSignIn();
            }
        });
    }

    private void updateMiniPlayerMargins() {
        // mini-player bottom margin setting
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        int miniPlayerBottomMargin = Helper.parseInt(
                sp.getString(PREFERENCE_KEY_MINI_PLAYER_BOTTOM_MARGIN, String.valueOf(DEFAULT_MINI_PLAYER_MARGIN)), DEFAULT_MINI_PLAYER_MARGIN);
        ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) findViewById(R.id.global_now_playing_card).getLayoutParams();
        int scaledMiniPlayerMargin = getScaledValue(DEFAULT_MINI_PLAYER_MARGIN);
        int scaledMiniPlayerBottomMargin = getScaledValue(miniPlayerBottomMargin);
        lp.setMargins(scaledMiniPlayerMargin, 0, scaledMiniPlayerMargin, scaledMiniPlayerBottomMargin);
    }

    public boolean isDarkMode() {
