public class AlertPopup extends AppCompatActivity {
    boolean mIsDestroyed;

    TextView mZone;
    TextView mCounter;

    Button mClose;
    Button mSilence;

    public static void showAlertPopup(String alertType, String city, Context context) {
        // User disabled this feature?
        if (!AppPreferences.getPopupEnabled(context)) {
            // Stop execution
            return;
        }

        // Type must be an "alert"
        if (!alertType.equals(AlertTypes.PRIMARY)) {
            // Stop execution
            return;
        }

        // Create new popup intent
        Intent popupIntent = new Intent();

        // Set class to popup activity
        popupIntent.setClass(context, AlertPopup.class);

        // Pass on zone
        popupIntent.putExtra(AlertPopupParameters.ALERT_CITY, city);

        // Clear top, set as new task
        popupIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        popupIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        popupIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);

        // Display popup activity
        context.startActivity(popupIntent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Load UI elements
        initializeUI();

        // Populate UI elements
        initializeAlert();

        // Volume keys should control alert volume
        Volume.setVolumeKeysAction(this);
    }

    void initializeUI() {
        // RTL action bar hack
        RTLSupport.mirrorActionBar(this);

        // Set up UI
        setContentView(R.layout.popup);

        // Cache UI objects
        mClose = (Button) findViewById(R.id.close);
        mSilence = (Button) findViewById(R.id.silence);
        mZone = (TextView) findViewById(R.id.zone);
        mCounter = (TextView) findViewById(R.id.counter);

        // Set up listeners
        initializeListeners();
    }

    void initializeListeners() {
        // Close button clicked
        mClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Stop the media service
                SoundLogic.stopSound(AlertPopup.this);

                // Close popup
                finish();
            }
        });

        // Silence button clicked
        mSilence.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Stop the media service
                SoundLogic.stopSound(AlertPopup.this);
            }
        });
    }

    void initializeAlert() {
        // Get alert city
        String city = getIntent().getStringExtra(AlertPopupParameters.ALERT_CITY);

        // None given?
        if (StringUtils.stringIsNullOrEmpty(city)) {
            return;
        }

        // Set title to city name
        setTitle(LocationData.getLocalizedCityName(city, this));

        // Get zone
        String zone = LocationData.getLocalizedZoneByCityName(city, this);

        // Unknown?
        if (StringUtils.stringIsNullOrEmpty(zone)) {
            return;
        }

        // Set cities to region cities
        mZone.setText(zone);

        // Get countdown in seconds
        int countdown = LocationData.getCityCountdown(city, this);

        // Start counting down
        scheduleRocketCountdown(countdown);
    }

    @Override
    protected void onUserLeaveHint() {
        super.onUserLeaveHint();

        // End activity
        if (!isFinishing()) {
            finish();
        }
    }

    void scheduleRocketCountdown(int seconds) {
        // Offset impact to account for delivery delay
        // Seconds = Seconds - Main.IMPACT_COUNTDOWN_OFFSET;

        // Calculate time to impact
        final long impactTimestamp = System.currentTimeMillis() + (seconds * 1000);

        // Schedule a new timer
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Activity died?
                        if (isFinishing() || mIsDestroyed) {
                            return;
                        }

                        // Update countdown
                        updateCountdownTimer(impactTimestamp);
                    }
                });
            }
        }, 0, 100);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // Avoid hiding invalid dialogs
        mIsDestroyed = true;
    }

    void updateCountdownTimer(long impactTimestamp) {
        // Get current time
        long currentTimestamp = System.currentTimeMillis();

        // Calculate seconds left to brace for impact
        int seconds = (int) Math.abs((impactTimestamp - currentTimestamp) / 1000);

        // Did rocket already impact?
        if (currentTimestamp <= impactTimestamp) {
            // Convert it
            updateCountdownTimerText(seconds, R.color.countdown_pre_impact);

        }
        else if (currentTimestamp > impactTimestamp) {
            // Number of seconds to wait after impact
            int postImpactSeconds = (Safety.POST_IMPACT_WAIT_MINUTES * 60);

            // Stop counting after X minutes
            if (seconds >= postImpactSeconds) {
                // Show green counter
                updateCountdownTimerText(postImpactSeconds, R.color.countdown_post_impact_safe);

                // Shut down activity after post impact + X seconds (padding)
                if (seconds >= postImpactSeconds + Alerts.ALERT_POPUP_DONE_PADDING) {
                    // Done with activity (shuts down screen as well)
                    finish();
                }

                // Stop execution
                return;
            }

            // Convert it
            updateCountdownTimerText(seconds, R.color.countdown_post_impact);
        }
    }

    void updateCountdownTimerText(int seconds, int color) {
        // Get human-readable minutes
        int minutes = seconds / 60;

        // Convert seconds to support the minutes
        seconds = seconds % 60;

        // Set text color
        mCounter.setTextColor(getResources().getColor(color));

        // Set countdown text
        mCounter.setText(String.format("%02d:%02d", minutes, seconds));
    }

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

        // Support for RTL languages
        RTLSupport.mirrorActionBar(this);
    }

    @Override
    protected void onPause() {
        super.onPause();

        // Fade out gradually
        overridePendingTransition(0, R.anim.fade_out);
    }

    void centerPopupInParent(Window Window) {
        // Fade out
        FrameLayout rootView = (FrameLayout) Window.getDecorView();

        // Get action bar layout
        View actionBarLayout = rootView.getChildAt(0);

        // Detach it from decor view
        rootView.removeView(actionBarLayout);

        // Create new frame layout
        FrameLayout wrapLayout = new FrameLayout(this);

        // Set params to WRAP_CONTENT
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);

        // Get 25 as PX value
        int dpToPixels = DensityUtil.convertDPToPixels(this, 25);

        // Set margins
        layoutParams.setMargins(dpToPixels, dpToPixels, dpToPixels, dpToPixels);

        // Set gravity to CENTER_VERTICAL
        layoutParams.gravity = Gravity.CENTER_VERTICAL;

        // Set layout params
        wrapLayout.setLayoutParams(layoutParams);

        // Add ActionBar to layout
        wrapLayout.addView(actionBarLayout);

        // Add it to root view
        rootView.addView(wrapLayout);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Get window object
        Window window = getWindow();

        // Turn screen on, show when locked and keep screen on
        window.addFlags(
                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        //| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        // Call parent
        centerPopupInParent(window);
    }

    public boolean onOptionsItemSelected(final MenuItem Item) {
        // Check item ID
        switch (Item.getItemId()) {
            // Home button?
            case android.R.id.home:
                onBackPressed();
        }

        return super.onOptionsItemSelected(Item);
    }
} public class Main extends AppCompatActivity {
    boolean mIsResumed;
    boolean mIsDestroyed;
    boolean mIsReloading;

    Button mImSafe;
    ListView mAlertsList;
    ProgressBar mLoading;
    MenuItem mLoadingItem;
    LinearLayout mNoAlerts;
    AlertAdapter mAlertsAdapter;

    List<Alert> mNewAlerts;
    List<Alert> mDisplayAlerts;
    SharedPreferences.OnSharedPreferenceChangeListener mBroadcastListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences Preferences, String Key) {
            // Asked for reload?
            if (Key.equalsIgnoreCase(MainActivityParameters.RELOAD_RECENT_ALERTS)) {
                reloadRecentAlerts();
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialize app UI
        initializeUI();

        // Start polling for recent alerts
        pollRecentAlerts();

        // Volume keys should control alert volume
        Volume.setVolumeKeysAction(this);

        // Start the app services
        ServiceManager.startAppServices(this);

        // Got any dialogs to display?
        showImportantDialogs();

        // Check for app version updates
        initializeUpdateChecker();

        // Always re-register FCM or Pushy on app start
        new RegisterPushAsync().execute();
    }

    void initializeUpdateChecker() {
        // Do it async
        new CheckForUpdatesAsync().execute();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        // Clear app notifications
        AppNotifications.clearAll(this);

        // RTL action bar hack
        RTLSupport.mirrorActionBar(this);
    }

    void initializeUI() {
        // RTL action bar hack
        RTLSupport.mirrorActionBar(this);

        // Set title manually (for override locale to work)
        setTitle(R.string.recentAlerts);

        // Set up UI
        setContentView(R.layout.main);

        // Get views by IDs
        mImSafe = (Button) findViewById(R.id.safe);
        mAlertsList = (ListView) findViewById(R.id.alerts);
        mLoading = (ProgressBar) findViewById(R.id.loading);
        mNoAlerts = (LinearLayout) findViewById(R.id.noAlerts);

        // Initialize alert list
        mNewAlerts = new ArrayList<>();
        mDisplayAlerts = new ArrayList<>();

        // Create alert adapter
        mAlertsAdapter = new AlertAdapter(this, mDisplayAlerts);

        // Attach it to list
        mAlertsList.setAdapter(mAlertsAdapter);

        // On-click listeners
        initializeListeners();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        // Avoid call to super(). Bug on API Level > 11.
    }

    void initializeListeners() {
        // Alert click listener
        mAlertsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                // Get alert by click position
                Alert alert = mDisplayAlerts.get(position);

                // No such alert?
                if (alert == null) {
                    return;
                }

                // Create new intent
                Intent alertView = new Intent();

                // Set class
                alertView.setClass(Main.this, AlertView.class);

                // Push extras
                alertView.putExtra(AlertViewParameters.ALERT_CITY, alert.city);
                alertView.putExtra(AlertViewParameters.ALERT_DATE_STRING, alert.dateString);

                // Show it
                startActivity(alertView);
            }
        });

        // No alerts image click listener
        mNoAlerts.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // DEBUG ONLY
                //AlertLogic.processIncomingAlert("חיפה והקריות 160", "alert", Main.this);
            }
        });

        // "I'm Safe" button click listener
        mImSafe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Prepare share intent
                Intent shareIntent = new Intent(Intent.ACTION_SEND);

                // Set as text/plain
                shareIntent.setType("text/plain");

                // Add text
                shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.imSafeMessage));

                // Send via WhatsApp?
                if (Singleton.getSharedPreferences(Main.this).getBoolean(getString(R.string.imSafeWhatsAppPref), false)) {
                    // WhatsApp installed?
                    if (WhatsApp.isAppInstalled(Main.this)) {
                        // Set WhatsApp package
                        shareIntent.setPackage(Integrations.WHATSAPP_PACKAGE);
                    }
                    else {
                        // Show toast
                        Toast.makeText(Main.this, getString(R.string.whatsAppNotInstalled), Toast.LENGTH_SHORT).show();
                    }
                }

                // Show chooser
                startActivity(Intent.createChooser(shareIntent, getString(R.string.imSafeDesc)));
            }
        });
    }

    @Override
    public void onConfigurationChanged(android.content.res.Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // Support for RTL languages
        RTLSupport.mirrorActionBar(this);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Save state
        mIsResumed = true;

        // Reload alerts manually
        reloadRecentAlerts();

        // Support for RTL languages
        RTLSupport.mirrorActionBar(this);

        // Clear app notifications
        AppNotifications.clearAll(this);

        // Ask user to enable GPS if necessary
        LocationDialogs.requestEnableLocationServices(this);

        // Register for broadcasts
        Broadcasts.subscribe(this, mBroadcastListener);
    }

    @Override
    protected void onPause() {
        super.onPause();

        // Save state
        mIsResumed = false;

        // Unregister for broadcasts
        Broadcasts.unsubscribe(this, mBroadcastListener);
    }

    void showImportantDialogs() {
        // Haven't displayed tutorial?
        if (!AppPreferences.getTutorialDisplayed(this)) {
            return;
        }

        // Haven't registered for notifications?
        if (!FCMRegistration.isRegistered(this) || !PushyRegistration.isRegistered(this)) {
            return;
        }

        // Did we enable a device integration but Bluetooth is disabled?
        if (BluetoothIntegration.isIntegrationEnabled(this) && !BluetoothIntegration.isBluetoothEnabled()) {
            // Ask user politely to enable Bluetooth
            BluetoothDialogs.showEnableBluetoothDialog(this);

            // Avoid duplicate dialogs
            return;
        }

        // Battery exemption dialog for Pushy
        if (PushyRegistration.isRegistered(this)) {
            // Android M (6) and up only
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // Get power manager instance
                PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

                // Check if app isn't already whitelisted from battery optimizations
                if (!powerManager.isIgnoringBatteryOptimizations(getPackageName())) {
                    // Get app name as string
                    String appName = getPackageManager().getApplicationLabel(getApplicationInfo()).toString();

                    // Instruct user to whitelist app from optimizations
                    new AlertDialog.Builder(this)
                            .setTitle("Disable battery optimizations")
                            .setMessage("If you'd like to receive notifications in the background, please click OK and select \"All apps\" -> " + appName + " -> Don't optimize.")
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    // Display the battery whitelist screen
                                    startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
                                }
                            })
                            .setNegativeButton("Cancel", null).show();
                }
            }
        }
    }

    void pollRecentAlerts() {
        // Schedule a new timer
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // App is running?
                        if (mIsResumed) {
                            // Reload every X seconds
                            reloadRecentAlerts();
                        }
                    }
                });
            }
        }, 0, 1000 * RecentAlerts.RECENT_ALERTS_POLLING_INTERVAL_SEC);
    }

    void reloadRecentAlerts() {
        // Not already reloading?
        if (!mIsReloading) {
            // Get recent alerts async
            new GetRecentAlertsAsync().execute();
        }
    }

    void initializeSettingsButton(Menu OptionsMenu) {
        // Add refresh in Action Bar
        MenuItem settingsItem = OptionsMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, getString(R.string.settings));

        // Set up the view
        settingsItem.setIcon(R.drawable.ic_settings);

        // Specify the show flags
        MenuItemCompat.setShowAsAction(settingsItem, MenuItem.SHOW_AS_ACTION_ALWAYS);

        // On click, go to Settings
        settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // Start settings activity
                goToSettings(false);

                // Consume event
                return true;
            }
        });
    }

    void goToSettings(boolean showZoneSelection) {
        // Prepare new intent
        Intent settingsIntent = new Intent();

        // Set settings class
        settingsIntent.setClass(Main.this, General.class);

        // Add area selection boolean
        settingsIntent.putExtra(SettingsEvents.SHOW_ZONE_SELECTION, showZoneSelection);

        // Start settings activity
        startActivity(settingsIntent);
    }

    void initializeLoadingIndicator(Menu OptionsMenu) {
        // Add refresh in Action Bar
        mLoadingItem = OptionsMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, getString(R.string.loading));

        // Set up the view
        MenuItemCompat.setActionView(mLoadingItem, R.layout.loading);

        // Specify the show flags
        MenuItemCompat.setShowAsAction(mLoadingItem, MenuItem.SHOW_AS_ACTION_ALWAYS);

        // Hide by default
        mLoadingItem.setVisible(false);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu OptionsMenu) {
        // Add loading indicator
        initializeLoadingIndicator(OptionsMenu);

        // Add settings button
        initializeSettingsButton(OptionsMenu);

        // Show the menu
        return true;
    }

    private int getRecentAlerts() {
        // Store JSON as string initially
        String alertsJSON;

        try {
            // Get it from /alerts
            alertsJSON = HTTP.get(API.API_ENDPOINT + "/alerts");
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Get recent alerts request failed", exc);

            // Return error code
            return R.string.apiRequestFailed;
        }

        // Prepare tmp object list
        List<Alert> recentAlerts;

        try {
            // Convert JSON to object
            recentAlerts = Singleton.getJackson().readValue(alertsJSON, new TypeReference<List<Alert>>() {
            });
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Get recent alerts request failed", exc);

            // Return error code
            return R.string.jsonFailed;
        }

        // Initialize date format
        SimpleDateFormat dateFormat = new SimpleDateFormat(Alerts.DATE_FORMAT);

        // For debugging purposes
        // Alert fake = new Alert();
        // fake.city = "אבו סנאן";
        // fake.date = 1562037706;

        // recentAlerts.add(fake);

        // Loop over alerts
        for (Alert alert : recentAlerts) {
            // Convert date to string
            alert.dateString = dateFormat.format(alert.date * 1000);

            // Convert area to friendly name
            alert.desc = LocationData.getLocalizedZoneWithCountdown(alert.city, this);

            // Localize it
            alert.localizedCity = LocationData.getLocalizedCityName(alert.city, this);
        }

        // Clear global list
        mNewAlerts.clear();

        // Add all the new alerts
        mNewAlerts.addAll(recentAlerts);

        // Success
        return 0;
    }

    private String checkForUpdates() {
        // Grab the update JSON
        String updateJson;

        try {
            // Get it from /update/android
            updateJson = HTTP.get(API.API_ENDPOINT + "/update/android");
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Get update info failed", exc);

            // Return error code
            return null;
        }

        // Convert into JSON
        VersionInfo updateInfo;

        try {
            // Convert JSON to object
            updateInfo = Singleton.getJackson().readValue(updateJson, com.red.alert.model.res.VersionInfo.class);
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Parsing update info failed", exc);

            // Stop execution
            return null;
        }

        // Don't show update dialog?
        if (!updateInfo.showDialog) {
            return null;
        }

        // Update available? (Higher version code)
        if (updateInfo.versionCode > AppVersion.getVersionCode(this)) {
            // Return newer version string for display
            return updateInfo.version;
        }

        // No need to update
        return null;
    }

    void toggleProgressBarVisibility(boolean visibility) {
        // Set loading visibility
        if (mLoadingItem != null) {
            mLoadingItem.setVisible(visibility);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // Avoid hiding invalid dialogs
        mIsDestroyed = true;
    }

    void invalidateAlertList() {
        // Clear global list
        mDisplayAlerts.clear();

        // Add all the new alerts
        mDisplayAlerts.addAll(mNewAlerts);

        // Invalidate the list
        mAlertsAdapter.notifyDataSetChanged();

        // No alerts? Show default
        if (mDisplayAlerts.size() == 0) {
            mNoAlerts.setVisibility(View.VISIBLE);
        }
        else {
            mNoAlerts.setVisibility(View.GONE);
        }
    }

    void showRegistrationSuccessDialog() {
        // Already displayed?
        if (AppPreferences.getTutorialDisplayed(this)) {
            return;
        }

        // Build the dialog
        AlertDialogBuilder.showGenericDialog(getString(R.string.pushRegistrationSuccess), getString(R.string.pushRegistrationSuccessDesc), this, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                // Start settings activity
                goToSettings(true);
            }
        });

        // Tutorial was displayed
        AppPreferences.setTutorialDisplayed(this);
    }

    public class GetRecentAlertsAsync extends AsyncTaskAdapter<Integer, String, Integer> {
        public GetRecentAlertsAsync() {
            // Prevent concurrent reload
            mIsReloading = true;

            // Show loading indicator
            toggleProgressBarVisibility(true);
        }

        @Override
        protected Integer doInBackground(Integer... Parameter) {
            // Try to get recent alerts
            return getRecentAlerts();
        }

        @Override
        protected void onPostExecute(Integer errorStringResource) {
            // No longer reloading
            mIsReloading = false;

            // Activity dead?
            if (isFinishing() || mIsDestroyed) {
                return;
            }

            // Hide loading
            mLoading.setVisibility(View.GONE);

            // Hide loading indicator
            toggleProgressBarVisibility(false);

            // Success?
            if (errorStringResource == 0) {
                // Invalidate the list
                invalidateAlertList();
            }
            else {
                // Show error toast
                Toast.makeText(Main.this, getString(errorStringResource), Toast.LENGTH_LONG).show();
            }
        }
    }

    public class RegisterPushAsync extends AsyncTaskAdapter<Integer, String, Exception> {
        ProgressDialog mLoading;

        public RegisterPushAsync() {
            // Fix progress dialog appearance on old devices
            mLoading = ProgressDialogCompat.getStyledProgressDialog(Main.this);

            // Prevent cancel
            mLoading.setCancelable(false);

            // Set default message
            mLoading.setMessage(getString(R.string.signing_up));

            // First time registering?
            if (!FCMRegistration.isRegistered(Main.this) || !PushyRegistration.isRegistered(Main.this)) {
                // Show the progress dialog
                mLoading.show();
            }
        }

        @Override
        protected Exception doInBackground(Integer... Parameter) {
            // Store exception from registration attempts
            Exception error = null;

            try {
                // Register for Pushy push notifications
                PushyRegistration.registerForPushNotifications(Main.this);
            }
            catch (Exception exc) {
                // Return exception to onPostExecute
                error = exc;
            }

            // Make sure we have Google Play Services installed
            if (!GooglePlayServices.isAvailable(Main.this)) {
                return new Exception("This app requires Google Play Services.");
            }

            // Keep track of previously-saved FCM token to detect changes and update the API
            String previousFirebaseToken = FCMRegistration.getRegistrationToken(Main.this);

            try {
                // Register for FCM push notifications
                String token = FCMRegistration.registerForPushNotifications(Main.this);

                // First time registering with the API?
                if (!RedAlertAPI.isRegistered(Main.this)) {
                    // Register with RedAlert API and store user ID & hash
                    RedAlertAPI.register(token,Main.this);
                }
                else {
                    // FCM token has changed?
                    if (!previousFirebaseToken.equals(FCMRegistration.getRegistrationToken(Main.this))) {
                        // Update token server-side
                        RedAlertAPI.updateToken(token, Main.this);
                    }
                }

                // First time subscribing with the API?
                if (!RedAlertAPI.isSubscribed(Main.this)) {
                    // Update notification preferences
                    RedAlertAPI.updateNotificationPreferences(Main.this);

                    // Subscribe for alerts based on current city/region selections
                    RedAlertAPI.subscribe(Main.this);
                }
            }
            catch (Exception exc) {
                error = exc;
            }

            // Return exc (if any)
            return error;
        }

        @Override
        protected void onPostExecute(Exception exc) {
            // Activity dead?
            if (isFinishing() || mIsDestroyed) {
                return;
            }

            // Hide loading
            if (mLoading.isShowing()) {
                mLoading.dismiss();
            }

            // Failed?
            if (exc != null) {
                // Log it
                Log.e(Logging.TAG, "Push registration failed", exc);

                // Build an error message
                String errorMessage = getString(R.string.pushRegistrationFailed) + "\n\n" + exc.getMessage() + "\n\n" + exc.getCause();

                // Build the dialog
                AlertDialogBuilder.showGenericDialog(getString(R.string.error), errorMessage, Main.this, null);
            }
            else {
                // Success, show success dialog if haven't yet
                showRegistrationSuccessDialog();
            }
        }
    }

    public class CheckForUpdatesAsync extends AsyncTaskAdapter<Integer, String, String> {
        @Override
        protected String doInBackground(Integer... Parameter) {
            // Try to check for updates
            return checkForUpdates();
        }

        @Override
        protected void onPostExecute(String newVersion) {
            // Activity dead?
            if (isFinishing() || mIsDestroyed) {
                return;
            }

            // Got a return code?
            if (!StringUtils.stringIsNullOrEmpty(newVersion)) {
                // Show update dialog
                UpdateDialogs.showUpdateDialog(Main.this, newVersion);
            }
        }
    }
} public class Logging {
    // Tag for all logcat logs
    public static String TAG = "RedAlert";
} public class Sound {
    public static final String SCHEME_URI_IDENTIFIER = "://";
    public static final String APP_SOUND_PREFIX = "com.red.alert://";
    // The AudioManager stream that sirens will be played on
    public static int STREAM_TYPE = AudioManager.STREAM_ALARM;
} public class MainActivityParameters {
    public static final String RELOAD_RECENT_ALERTS = "ReloadRecentAlerts";
} public class RocketNotificationParameters {
    public static final String NOTIFICATION_DELETED_ACTION = "NOTIFICATION_DELETED";
} public class SoundLogic {
    static MediaPlayer mPlayer;
    static final int ALARM_CUTOFF_SECONDS = 5;

    public static boolean shouldPlayAlertSound(String alertType, Context context) {
        // No type?
        if (StringUtils.stringIsNullOrEmpty(alertType)) {
            return false;
        }

        // No sound for system message
        if (alertType.equals(AlertTypes.SYSTEM)) {
            return false;
        }

        // Get the audio manager
        AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE);

        // Phone is on silent?
        if (audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
            // Override silent mode not allowed?
            if (!shouldOverrideSilentMode(alertType, context)) {
                return false;
            }
        }

        // Play sound
        return true;
    }

    public static int getSoundStreamType(Context context) {
        // Get the audio manager
        AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE);

        // In call?
        if (audioManager.getMode() == AudioManager.MODE_IN_CALL) {
            // Play in tiny speaker
            // I think we disabled this because it's played in that speaker already by default
            // return AudioManager.STREAM_VOICE_CALL;
        }

        // Return default stream type
        return Sound.STREAM_TYPE;
    }

    public static Uri getAlertSound(String alertType, String overrideSound, Context context) {
        // Override sound selection?
        if (overrideSound != null) {
            // Convert to URI and return it
            return getSoundURI(overrideSound, context);
        }

        // Get selected sound
        String soundPreference = getSoundPreference(alertType, context);

        // Get sound file path
        String selectedSound = Singleton.getSharedPreferences(context).getString(soundPreference, getDefaultSound(alertType, context));

        // No sound selected?
        if (StringUtils.stringIsNullOrEmpty(selectedSound)) {
            // Nothing to play
            return null;
        }

        // Get path to sound resource
        return getSoundURI(selectedSound, context);
    }

    static Uri getSoundURI(String uri, Context context) {
        // No URI? Nothing to do here
        if (StringUtils.stringIsNullOrEmpty(uri)) {
            return null;
        }

        // Support for old alarms (without the scheme identifier)
        if (!uri.contains(Sound.SCHEME_URI_IDENTIFIER)) {
            return getAppSoundByResourceName(uri, context);
        }

        // Scheme without an actual file?
        if (uri.equals(Sound.APP_SOUND_PREFIX)) {
            return null;
        }

        // com.red.alert:// scheme support
        String soundPrefix = Sound.APP_SOUND_PREFIX;

        // Does it start with this prefix?
        if (uri.startsWith(soundPrefix)) {
            // Extract sound resource name without scheme
            uri = uri.substring(uri.indexOf(soundPrefix) + soundPrefix.length());

            // Convert to resource URI
            return getAppSoundByResourceName(uri, context);
        }

        // Try to parse the URI
        return Uri.parse(uri);
    }

    static Uri getAppSoundByResourceName(String resourceName, Context context) {
        // Convert to resource ID
        int resourceID = context.getResources().getIdentifier("raw/" + resourceName, "raw", context.getPackageName());

        // Finally, get path to sound file
        Uri alarmSoundURI = Uri.parse("android.resource://" + context.getPackageName() + "/" + resourceID);

        // Return sound URI
        return alarmSoundURI;
    }

    static String getDefaultSound(String alertType, Context context) {
        // By default, regular sound
        String soundDefault = context.getString(R.string.defaultSound);

        // Secondary alert?
        if (alertType.equals(AlertTypes.SECONDARY)) {
            // Set new value
            soundDefault = context.getString(R.string.defaultSecondarySound);
        }

        // Return default sound
        return soundDefault;
    }

    static String getSoundPreference(String alertType, Context context) {
        // By default, primary preference
        String soundPreference = context.getString(R.string.soundPref);

        // Secondary alert?
        if (alertType.equals(AlertTypes.SECONDARY)) {
            // Set new pref
            soundPreference = context.getString(R.string.secondarySoundPref);
        }

        // Return default pref
        return soundPreference;
    }

    public static boolean shouldOverrideSilentMode(String alertType, Context context) {
        // Testing?
        if (alertType.equals(AlertTypes.TEST_SOUND) || alertType.equals(AlertTypes.TEST_SECONDARY_SOUND)) {
            return true;
        }

        // Get enabled / disabled setting
        boolean overrideSilentMode = Singleton.getSharedPreferences(context).getBoolean(context.getString(R.string.silentOverridePref), true);

        // Secondary alert?
        if (alertType.equals(AlertTypes.SECONDARY)
                || alertType.equals(AlertTypes.TEST_SECONDARY_SOUND)) {
            overrideSilentMode = Singleton.getSharedPreferences(context).getBoolean(context.getString(R.string.secondarySilentOverridePref), false);
        }

        // Return setting value
        return overrideSilentMode;
    }

    public static boolean isSoundCurrentlyPlaying(String alertType, Context context) {
        // Primary rocket notification?
        if (alertType.equals(AlertTypes.PRIMARY)) {
            return primaryAlertCurrentlyPlaying(context);
        }

        // Secondary rocket notification?
        if (alertType.equals(AlertTypes.SECONDARY)) {
            return secondaryAlertCurrentlyPlaying(context);
        }

        // Not playing
        return false;
    }

    public static boolean secondaryAlertCurrentlyPlaying(Context context) {
        // Set currently playing
        boolean currentlyPlaying = false;

        // Do we have an alert currently playing? (Played in last X sec)
        if (getAlertLastPlayedTimestamp(context.getString(R.string.lastAlertPref), context) > getCurrentPlayingAlertCutoff()) {
            currentlyPlaying = true;
        }

        // Do we have a sound currently playing? (Played in last X sec)
        if (getAlertLastPlayedTimestamp(context.getString(R.string.lastSecondaryAlertPref), context) > getCurrentPlayingAlertCutoff()) {
            currentlyPlaying = true;
        }

        // Save current timestamp
        saveAlertCurrentlyPlaying(context.getString(R.string.lastSecondaryAlertPref), context);

        // No sound playing
        return currentlyPlaying;
    }

    public static boolean primaryAlertCurrentlyPlaying(Context context) {
        // Set currently playing
        boolean currentlyPlaying = false;

        // Do we have a sound currently playing? (Played in last X sec)
        if (getAlertLastPlayedTimestamp(context.getString(R.string.lastAlertPref), context) > getCurrentPlayingAlertCutoff()) {
            currentlyPlaying = true;
        }

        // Save current timestamp
        saveAlertCurrentlyPlaying(context.getString(R.string.lastAlertPref), context);

        // No sound playing
        return currentlyPlaying;
    }

    static void saveAlertCurrentlyPlaying(String preference, Context context) {
        // Edit shared preferences
        SharedPreferences.Editor edit = Singleton.getSharedPreferences(context).edit();

        // Store current ms in settings with the given property,
        // so that listeners will be notified
        edit.putLong(preference, System.currentTimeMillis());

        // Save and flush
        edit.commit();
    }

    static long getAlertLastPlayedTimestamp(String preference, Context context) {
        // Get last played from preferences
        return Singleton.getSharedPreferences(context).getLong(preference, 0);
    }

    static long getCurrentPlayingAlertCutoff() {
        // Alarm played in past X seconds?
        return System.currentTimeMillis() - (1000 * ALARM_CUTOFF_SECONDS);
    }

    public static void playSound(String alertType, String alertSound, Context context) {
        // Should we play it?
        if (!shouldPlayAlertSound(alertType, context)) {
            return;
        }

        // Avoid secondary override
        if (isSoundCurrentlyPlaying(alertType, context)) {
            return;
        }

        // Get path to resource
        Uri alarmSoundURI = getAlertSound(alertType, alertSound, context);

        // Invalid sound URI?
        if (alarmSoundURI == null) {
            return;
        }

        // Override volume (Also to set the user's chosen volume)
        VolumeLogic.setStreamVolume(alertType, context);

        // Play sound
        playSoundURI(alarmSoundURI, context);

        // Vibrate depending on type
        Vibration.issueVibration(alertType, context);
    }

    public static void stopSound(Context context) {
        // Got a player?
        if (mPlayer != null) {
            // Still playing?
            if (mPlayer.isPlaying()) {
                // Stop playing
                mPlayer.stop();

                // Reset media player
                mPlayer.reset();
            }

            // Release resources associated
            mPlayer.release();

            // Reinitialize next time
            mPlayer = null;
        }

        // Stop vibration
        Vibration.stopVibration(context);
    }

    static void playSoundURI(Uri alarmSoundUi, Context context) {
        // No URI?
        if (alarmSoundUi == null) {
            return;
        }

        // Already initialized or currently playing?
        stopSound(context);

        // Create new MediaPlayer
        mPlayer = new MediaPlayer();

        // Wake up processor
        mPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);

        // Set stream type
        mPlayer.setAudioStreamType(getSoundStreamType(context));

        try {
            // Set URI data source
            mPlayer.setDataSource(context, alarmSoundUi);

            // Prepare media player
            mPlayer.prepare();
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Media player preparation failed", exc);

            // Show visible error toast
            Toast.makeText(context, exc.toString(), Toast.LENGTH_LONG).show();
        }

        // Actually start playing
        mPlayer.start();
    }
} public class BluetoothIntegration {
    public static void notifyDevices(String alertType, Context context) {
        // Type must be an "alert" or "test"
        if (alertType.equals(AlertTypes.PRIMARY) || alertType.equals(AlertTypes.TEST)) {
            // Check for BLE support + enabled Bluetooth controller
            if (!isBLESupported(context) || !isBluetoothEnabled()) {
                // Stop execution
                return;
            }

            // Vibrate + LED for Mi Band (if enabled)
            MiBandIntegration.notifyMiBand(context);
        }
    }

    public static boolean isIntegrationEnabled(Context context) {
        // Check if Mi Band integration is enabled (and add more devices in the future)
        return AppPreferences.getMiBandIntegrationEnabled(context);
    }

    public static boolean isBLESupported(Context context) {
        // Check whether Bluetooth is supported on the device
        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
            // No Bluetooth support
            return false;
        }

        // Check whether BLE is supported on the device
        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            // No BLE support
            return false;
        }

        // Check Android version (BluetoothGatt requires API Level 18+)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            return false;
        }

        // We're good
        return true;
    }

    public static boolean isBluetoothEnabled() {
        // Must call Looper.prepare() due to ICS bug
        // And we can't call the function from UI thread when we receive a push notification)
        // http://stackoverflow.com/questions/5920578/bluetoothadapter-getdefault-throwing-runtimeexception-while-not-in-activity

        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        // Get bluetooth adapter
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // Null means device doesn't support Bluetooth
        if (bluetoothAdapter == null) {
            // No Bluetooth support
            return false;
        }
        else {
            // Check if it's enabled and return the result
            return bluetoothAdapter.isEnabled();
        }
    }
} public class NotificationDeletedReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Got deleted notification event?
        if (RocketNotificationParameters.NOTIFICATION_DELETED_ACTION.equals(intent.getAction())) {
            // Silence currently playing sound
            SoundLogic.stopSound(context);
        }
    }
} public class Broadcasts {
    public static void subscribe(Context context, SharedPreferences.OnSharedPreferenceChangeListener listener) {
        // Listen for preference changes
        Singleton.getSharedPreferences(context).registerOnSharedPreferenceChangeListener(listener);
    }

    public static void unsubscribe(Context context, SharedPreferences.OnSharedPreferenceChangeListener listener) {
        // Unsubscribe from preference changes
        Singleton.getSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(listener);
    }

    public static void publish(Context context, String property) {
        // Edit shared preferences
        SharedPreferences.Editor edit = Singleton.getSharedPreferences(context).edit();

        // Store current ms in settings with the given property,
        // so that listeners will be notified
        edit.putLong(property, System.currentTimeMillis());

        // Save and flush
        edit.commit();
    }
} public class StringUtils {
    public static boolean stringIsNullOrEmpty(String inputString) {
        // String is null? return true
        if (inputString == null) {
            return true;
        }

        // String is empty? true
        if (inputString.trim().equals("")) {
            return true;
        }

        // String is not empty
        return false;
    }

    public static String implode(String separator, List<String> data) {
        // No data?
        if (data.size() == 0) {
            return "";
        }

        // Create temp StringBuilder
        StringBuilder builder = new StringBuilder();

        // Loop over list except last item
        for (int i = 0; i < data.size() - 1; i++) {
            // Append current item
            builder.append(data.get(i));

            // Append separator
            builder.append(separator);
        }

        // Add last item without separator
        builder.append(data.get(data.size() - 1));

        // Return string
        return builder.toString();
    }

    public static String regexMatch(String source, String compilePattern) {
        // Compile regex
        Pattern compiledPattern = Pattern.compile(compilePattern);

        // Match against source input
        Matcher matcher = compiledPattern.matcher(source);

        // Prepare match object
        String match = "";

        // Did we find anything?
        if (matcher.find()) {
            // Get first group
            match = matcher.group(1);
        }

        // Return match
        return match;
    }
}
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import me.pushy.sdk.Pushy;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.red.alert.R;
import com.red.alert.activities.AlertPopup;
import com.red.alert.activities.Main;
import com.red.alert.config.Logging;
import com.red.alert.config.Sound;
import com.red.alert.logic.communication.intents.MainActivityParameters;
import com.red.alert.logic.communication.intents.RocketNotificationParameters;
import com.red.alert.logic.feedback.sound.SoundLogic;
import com.red.alert.logic.integration.BluetoothIntegration;
import com.red.alert.receivers.NotificationDeletedReceiver;
import com.red.alert.utils.communication.Broadcasts;
import com.red.alert.utils.formatting.StringUtils;
package com.red.alert.logic.notifications;





public class RocketNotifications {
    // Silent notification channel config for no-sound alerts
    public static final String SILENT_NOTIFICATION_CHANNEL_ID = "redalert_silent";
    public static final String SILENT_NOTIFICATION_CHANNEL_NAME = "Silent Notifications";

    public static void notify(Context context, String city, String notificationTitle, String notificationContent, String alertType, String overrideSound) {
        // Get notification manager
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);

        // In case there is no content
        if (StringUtils.stringIsNullOrEmpty(notificationContent)) {
            // Move title to content
            notificationContent = notificationTitle;

            // Set title as app name
            notificationTitle = context.getString(R.string.appName);
        }

        // Create a new notification and style it
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
                .setTicker(notificationContent)
                .setAutoCancel(true)
                .setContentTitle(notificationTitle)
                .setContentText(notificationContent)
                .setLights(Color.RED, 1000, 1000)
                .setSmallIcon(R.drawable.ic_notify)
                .setColor(Color.parseColor("#ff4032"))
                .setCategory(NotificationCompat.CATEGORY_ALARM)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(notificationContent))
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher));

        // Handle notification delete
        builder.setDeleteIntent(getNotificationDeletedReceiverIntent(context));

        // No click event for test notifications
        if (!alertType.contains("test")) {
            // Handle notification click
            builder.setContentIntent(getNotificationIntent(context));
        }

        // Generate a notification ID based on the unique hash-code of the alert zone
        int notificationID = notificationTitle.hashCode();

        // Cancel previous notification for same alert zone
        notificationManager.cancel(notificationID);

        // Automatically configure notification channel (if required)
        setNotificationChannel(alertType, builder, context);

        try {
            // Issue the notification
            notificationManager.notify(notificationID, builder.build());
        }
        catch (Exception exc) {
            // Log it
            Log.e(Logging.TAG, "Rocket notification failed", exc);

            // Show it as a toast message
            Toast.makeText(context, notificationTitle + " - " + notificationContent, Toast.LENGTH_LONG).show();
        }

        // Play alert sound (if applicable)
        SoundLogic.playSound(alertType, overrideSound, context);

        // Show alert popup (if applicable)
        AlertPopup.showAlertPopup(alertType, city, context);

        // Notify BLE devices (if applicable)
        BluetoothIntegration.notifyDevices(alertType, context);

        // Reload recent alerts (if main activity is open)
        Broadcasts.publish(context, MainActivityParameters.RELOAD_RECENT_ALERTS);
    }

    private static PendingIntent getNotificationDeletedReceiverIntent(Context context) {
        // Prepare delete intent
        Intent deleteIntent = new Intent(context, NotificationDeletedReceiver.class);

        // Set action
        deleteIntent.setAction(RocketNotificationParameters.NOTIFICATION_DELETED_ACTION);

        // Get broadcast receiver
        return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
    }

    public static PendingIntent getNotificationIntent(Context context) {
        // Prepare notification intent
        Intent notificationIntent = new Intent(context, Main.class);