com.appsimobile.appsii.Appsi.java Source code

Java tutorial

Introduction

Here is the source code for com.appsimobile.appsii.Appsi.java

Source

/*
 * Copyright 2015. Appsi Mobile
 *
 * 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.appsimobile.appsii;

import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.CircularArray;
import android.support.v4.widget.ScrollerCompat;
import android.util.Log;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.RelativeLayout;

import com.appsimobile.appsii.Sidebar.SidebarListener;
import com.appsimobile.appsii.SidebarHotspot.SwipeListener;
import com.appsimobile.appsii.dagger.AppInjector;
import com.appsimobile.appsii.dagger.AppsiComponent;
import com.appsimobile.appsii.dagger.AppsiInjector;
import com.appsimobile.appsii.dagger.AppsiModule;
import com.appsimobile.appsii.dagger.DaggerAppsiComponent;
import com.appsimobile.appsii.hotspotmanager.ManageHotspotsActivity;
import com.appsimobile.appsii.icontheme.iconpack.ActiveIconPackInfo;
import com.appsimobile.appsii.module.home.config.HomeItemConfigurationHelper;
import com.appsimobile.appsii.module.home.provider.HomeContract;
import com.appsimobile.appsii.permissions.PermissionUtils;
import com.appsimobile.appsii.plugins.IconCache;
import com.appsimobile.appsii.preference.PreferenceHelper;

import java.lang.ref.WeakReference;

import javax.inject.Inject;

/**
 * Appsii's main service.
 * <p/>
 * To see the status: adb shell dumpsys activity services com.appsimobile.appsii
 */
public class Appsi extends Service implements OnSharedPreferenceChangeListener, HotspotHelperListener,
        SidebarListener, PopupLayer.PopupLayerListener, Sidebar.OnCancelCloseListener {

    /**
     * Notification Ids
     */
    public static final int ONGOING_NOTIFICATION_ID = 11;
    /**
     * Will be sent to Appsi to exit the main service
     */
    public static final String ACTION_STOP_APPSI = BuildConfig.APPLICATION_ID + ".ACTION_STOP_APPSI";
    /**
     * Will be sent to Appsi to restart the main service. For example after a theme change
     */
    public static final String ACTION_RESTART_APPSI = BuildConfig.APPLICATION_ID + ".ACTION_RESTART_APPSI";
    /**
     * Broadcast to send to Appsi to resume all hotspots
     */
    public static final String ACTION_UNSUSPEND = BuildConfig.APPLICATION_ID + ".UNSUSPEND_APPSI";
    /**
     * Sent to Appsi to close the sidebar
     */
    public static final String ACTION_CLOSE_SIDEBAR = BuildConfig.APPLICATION_ID + ".action_close_sidebar";
    /**
     * Sent to Appsi to close the sidebar
     */
    public static final String ACTION_TRY_PAGE = BuildConfig.APPLICATION_ID + ".action_try";
    /**
     * Broadcast to get the status from Appsi
     */
    public static final String ACTION_ORDERED_BROADCAST_RUNNING = "com.appsimobile.appsii.ACTION_RUNNING";
    public static final int RESULT_RUNNING_STATUS_SUSPENDED = 3;

    // ORDERED BROADCASTS
    public static final int RESULT_RUNNING_STATUS_ENABLED = 4;
    public static final int RESULT_RUNNING_STATUS_DISABLED = 5;
    public static final String ACTION_LOCAL_OPEN_SIDEBAR_FROM_SHORTCUT = "com.appsimobile.appsii.ACTION_LOCAL_OPEN_SIDEBAR_FROM_SHORTCUT";
    /**
     * FLAG for appsi to indicate it should open from the left
     */
    public static final int OPEN_FLAG_LEFT = 2;
    /**
     * FLAG for appsi to indicate it should open from the right
     */
    public static final int OPEN_FLAG_RIGHT = 4;
    /**
     * FLAG for appsi to indicate it should open from the right
     */
    public static final int OPEN_FLAG_LIKE_NOTIFICATION_BAR = 8;
    static final int MESSAGE_CLOSE_SIDEBAR = 101;
    private static final String TAG = "Appsi";

    // RECEIVERS
    /**
     * True if Appsi was suspended by the user
     */
    static volatile boolean mSuspended = false;

    /**
     * Used in the binder to determine if Appsi was already created.
     * This will be used to determine it's status
     */
    static volatile boolean mCreated = false;

    private final AccelerateInterpolator mOutInterpolator = new AccelerateInterpolator();

    // CONFIGURATION OPTIONS

    private final DecelerateInterpolator mInInterpolator = new DecelerateInterpolator();

    /**
     * Receives and handles the following LOCAL broadcasts:
     * {@link #ACTION_LOCAL_OPEN_SIDEBAR_FROM_SHORTCUT}
     */
    LocalReceiver mLocalActionsReceiver;

    /**
     * The percentage of screen space the sidebar should be wide
     */
    int mSidebarPercentage;

    /**
     * A reference to the action sidebar view
     */
    @Inject
    Sidebar mSidebar;

    // VIEWS

    /**
     * The layer we add views to. This layer automatically handles things like
     * showing and hiding the screen dimming
     */
    @Inject
    PopupLayer mPopupLayer;

    private final Animator.AnimatorListener mCloseListener = new AnimatorAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            mPopupLayer.removePopupChild(mSidebar);
            mSidebar.setTranslationX(0);
        }
    };

    // RUNTIME STATE

    /**
     * A handler used to post certain events to. And for content observers
     */
    Handler mHandler;

    /**
     * Handles changes in the hotspots. Will remove, add and update hotspots as
     * needed.
     */
    private final ContentObserver mHotspotsContentObserver = new ContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange) {
            onChange(selfChange, null);
        }

        // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            // simple reload the hotspots
            reloadHotspots();
        }

    };

    /**
     * Used to query the keyguard status. When the key-guard is showing Appsi will
     * not be shown.
     */
    @Inject
    KeyguardManager mKeyguardManager;

    /**
     * The window manager. This is a system service used to show views on top of
     * other apps.
     */
    @Inject
    WindowManager mWindowManager;

    @Inject
    IconCache mIconCache;

    @Inject
    PermissionUtils mPermissionUtils;

    /**
     * The total number of plugins currently added.
     */
    int mConfiguredPluginCount;

    /**
     * The last known configuration appsi was shown in
     */
    Configuration mLastConfiguration;

    /**
     * A view that is dragged across the screen that looks like the notification
     * opening handle.
     */
    NotificationLikeOpener mNotificationLikeOpener;

    AnimationListener mScrollAnimationListener;
    /**
     * The shared preferences for Appsi
     */
    SharedPreferences mPrefs;
    /**
     * Handles things like showing/hiding the hotspots and flashing the hotspots.
     * In it's early days, Appsi had two operating modes, one for a hotspot per
     * plugin and one for a single hotspot. This was removed later when Appsi
     * evolved.
     */
    @Inject
    AbstractHotspotHelper mHotspotHelper;
    @Inject
    LoaderManagerImpl mLoaderManager;
    @Inject
    PreferenceHelper mPreferenceHelper;
    @Inject
    HomeItemConfigurationHelper mHomeItemConfigurationHelper;
    AppsiComponent mApplicationComponent;
    /**
     * receives and handles several broadcasts and updates Appsi accordingly. This
     * mostly involves things like removing an open sidebar when the screen is turned
     * of, closing system dialogs when requested, day-dreams etc.
     * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE}
     * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
     * {@link android.content.Intent#ACTION_USER_PRESENT}
     * {@link android.content.Intent#ACTION_DREAMING_STARTED}
     * {@link android.content.Intent#ACTION_SCREEN_ON}
     * {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}
     * {@link android.content.Intent#ACTION_SCREEN_OFF}
     */
    private ExternalEventsListener mExternalEventsReceiver;
    /**
     * Handles the following LOCAL broadcasts:
     * {@link #ACTION_ORDERED_BROADCAST_RUNNING}
     * {@link #ACTION_STOP_APPSI}
     */
    private AppsiStatusBroadcastReceiver mIsRunningBroadcastReceiver;
    /**
     * Handles plugin installation events
     */
    private BroadcastReceiver mAppStatusReceiver;
    /**
     * True while the sidebar is visible.
     */
    private boolean mSidebarVisible;
    /**
     * The hotspot configurations that have been found in the
     * appsi database.
     */
    private CircularArray<HotspotItem> mHotspotItems;

    public Appsi() {

    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate() called with: " + "");
        super.onCreate();

        Log.d(TAG, "onCreate() create shared prefs");
        mPrefs = AppInjector.provideSharedPreferences();

        Context context = ThemingUtils.createContextThemeWrapper(this, mPrefs);

        Log.d(TAG, "onCreate() initializeDagger");
        initializeDagger(context);

        mHotspotHelper.setCallback(this);

        Log.d(TAG, "onCreate() configure popup layer");
        mPopupLayer.setPopuplayerListener(this);

        mNotificationLikeOpener = new NotificationLikeOpener(this);
        Log.d(TAG, "onCreate() create configuration");
        mLastConfiguration = new Configuration(getResources().getConfiguration());

        Log.d(TAG, "onCreate() configure sidebar");
        mSidebar.setOnCancelCloseListener(this);
        mSidebar.setSidebarListener(this);

        Log.d(TAG, "onCreate() preferences");
        mSidebarPercentage = mPreferenceHelper.getSidebarWidth();

        int value = mPreferenceHelper.getSidebarDimLevel();
        updateDefaultDimColor(ThemingUtils.getPercentage(value));

        mPrefs.registerOnSharedPreferenceChangeListener(this);

        Log.d(TAG, "onCreate() create External events receiver");
        mExternalEventsReceiver = new ExternalEventsListener();
        mExternalEventsReceiver.register(this);

        Log.d(TAG, "onCreate() create Runnig receiver");
        mIsRunningBroadcastReceiver = new AppsiStatusBroadcastReceiver();
        mIsRunningBroadcastReceiver.register(this);

        Log.d(TAG, "onCreate() create status receiver");
        mAppStatusReceiver = AppStatusReceiver.register(this);

        Log.d(TAG, "onCreate() create local receiver");
        mLocalActionsReceiver = new LocalReceiver();
        mLocalActionsReceiver.register(this);

        Log.d(TAG, "onCreate() Handler");
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == MESSAGE_CLOSE_SIDEBAR) {
                    onCloseSidebar();
                }
                return false;
            }
        });

        // listen for changes in the hotspots
        Log.d(TAG, "onCreate() registering contentObserver");
        getContentResolver().registerContentObserver(HomeContract.Hotspots.CONTENT_URI, true,
                mHotspotsContentObserver);

        // Now that appsi has been initialized, scan the hotspot config
        Log.d(TAG, "onCreate() Load hotspots");
        reloadHotspots();
        mCreated = true;
        Log.d(TAG, "onCreate() completed onCreate");
    }

    private void updateDefaultDimColor(float alpha) {
        mPopupLayer.setDefaultDimAlpha(alpha);
    }

    @Override
    public void onCloseSidebar() {
        // remove any possible messages that are still scheduled to close Appsii
        mHandler.removeMessages(MESSAGE_CLOSE_SIDEBAR);

        if (mSidebarVisible) {
            mSidebarVisible = false;

            boolean left = mSidebar.getIsLeft();

            int sidebarWidth = getSidebarWidth();

            mSidebar.animate().translationX(left ? -sidebarWidth : sidebarWidth).setListener(mCloseListener)
                    .setInterpolator(mOutInterpolator).setDuration(150);
            mNotificationLikeOpener.mRemoveAfterScrollEnd = false;
            mSidebar.onSidebarClosing();
        }
        if (mLoaderManager.isStarted()) {
            mLoaderManager.doStop();
        }
    }

    /**
     * Start an AsyncTask that will load the hotspot configurations.
     * This will call {@link #onHotspotsLoaded(CircularArray)} when finished
     */
    void reloadHotspots() {
        HotspotLoader loader = new HotspotLoader(this);
        loader.execute();
    }

    private int getSidebarWidth() {
        int w = mWindowManager.getDefaultDisplay().getWidth();
        int h = mWindowManager.getDefaultDisplay().getHeight();
        int sw = Math.min(w, h);
        return (sw * mSidebarPercentage) / 100;
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() called with: " + "intent = [" + intent + "], flags = [" + flags
                + "], startId = [" + startId + "]");
        startAppsiService();
        // force initialize the home config to make everything a bit smoother
        // and this makes sure we don't have to wait for it when opening the
        // sidebar for the first time.
        // mHomeItemConfigurationHelper

        String action = intent == null ? null : intent.getAction();
        if (ACTION_TRY_PAGE.equals(action)) {

            HotspotPageEntry entry = intent.getParcelableExtra("entry");
            HotspotItem hotspotItem = intent.getParcelableExtra("hotspot");
            if (entry != null && hotspotItem != null) {
                CircularArray<HotspotPageEntry> entries = new CircularArray<>(1);
                entries.addFirst(entry);
                openSidebar(hotspotItem, entries, 0, true);
            }
        }

        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalActionsReceiver);

        unregisterReceiver(mExternalEventsReceiver);
        unregisterReceiver(mAppStatusReceiver);
        unregisterReceiver(mIsRunningBroadcastReceiver);

        getContentResolver().unregisterContentObserver(mHotspotsContentObserver);

        mPopupLayer.setPopuplayerListener(null);
        mSidebar.setSidebarListener(null);

        mSidebar = null;
        mPopupLayer = null;

        mSuspended = false;
        mHotspotHelper.removeHotspots();
        mHotspotHelper.onDestroy();

        Intent i = new Intent(AppsiiUtils.ACTION_APPSI_STATUS_CHANGED);
        sendBroadcast(i);
    }

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

        int diff = mLastConfiguration.diff(newConfig);

        // handle layout-direction change

        boolean directionChanged = (diff
                & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == ActivityInfo.CONFIG_LAYOUT_DIRECTION;
        boolean localeChanged = (diff & ActivityInfo.CONFIG_LOCALE) == ActivityInfo.CONFIG_LOCALE;
        boolean orientationChanged = newConfig.orientation != mLastConfiguration.orientation;

        mLastConfiguration = new Configuration(newConfig);

        if (directionChanged || localeChanged) {
            Log.i("Appsi", "restarting because of direction or locale change");
            restartAppsiService();
            return;
        }

        if (orientationChanged) {
            mHotspotHelper.onOrientationChanged();
        }
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        if (mSidebar != null) {
            mSidebar.onTrimMemory(level);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void startAppsiService() {
        Log.d(TAG, "Starting Appsi");
        updateNotificationStatus();

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent i = new Intent(AppsiiUtils.ACTION_STARTED);
                sendBroadcast(i);
            }
        }, 500);
    }

    public SidebarHotspot.SwipeListener openSidebar(HotspotItem conf, CircularArray<HotspotPageEntry> entries,
            int flags, boolean animate) {

        LockableAsyncQueryHandler.lock();
        mSidebar.setSidebarOpening(true);

        if (!mLoaderManager.isStarted()) {
            mLoaderManager.doStart();
        }

        boolean fullscreen = mHotspotHelper.getTopOffset() == 0;

        mSidebar.setInFullScreenMode(fullscreen);

        mNotificationLikeOpener.mRemoveAfterScrollEnd = false;
        mScrollAnimationListener = null;
        mSidebar.animate().cancel();

        // users reported this may happen
        if (mSidebarVisible) {
            mPopupLayer.forceClose();
            mSidebarVisible = false;
        }

        int sidebarWidth = getSidebarWidth();

        mSidebarVisible = true;

        if (conf == null) {
            conf = getDefaultConfiguration();
        }

        SwipeListener result = null;
        boolean openLikeNotificationBar = (flags & OPEN_FLAG_LIKE_NOTIFICATION_BAR) != 0;
        mSidebar.setTag(conf);
        boolean left = conf.mLeft;

        boolean forceLeft = (flags & OPEN_FLAG_LEFT) != 0;
        boolean forceRight = (flags & OPEN_FLAG_RIGHT) != 0;

        if (forceLeft) {
            left = true;
        }
        if (forceRight) {
            left = false;
        }
        mSidebar.setIsLeft(left);

        View v = mSidebar.findViewById(R.id.sidebar_content);

        RelativeLayout.LayoutParams sidebarLayoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
        sidebarLayoutParams.width = sidebarWidth;

        View container = mSidebar.findViewById(R.id.sidebar_container);
        RelativeLayout.LayoutParams slp = (RelativeLayout.LayoutParams) container.getLayoutParams();

        if (left) {
            slp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0);
            slp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
            sidebarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0);
            sidebarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
        } else {
            slp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
            slp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
            sidebarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
            sidebarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
        }

        if (animate && !openLikeNotificationBar) {

            mNotificationLikeOpener.cancel();

            if (left) {
                mSidebar.setTranslationX(-sidebarWidth);
                mSidebar.animate().setInterpolator(mInInterpolator).setDuration(400).setListener(null)
                        .translationX(0);
                mSidebar.findViewById(R.id.sidebar_left_shadow).setVisibility(View.GONE);
                mSidebar.findViewById(R.id.sidebar_right_shadow).setVisibility(View.VISIBLE);
            } else {
                mSidebar.setTranslationX(sidebarWidth);
                mSidebar.animate().setDuration(400).setListener(null).setInterpolator(mInInterpolator)
                        .translationX(0);
                mSidebar.findViewById(R.id.sidebar_left_shadow).setVisibility(View.VISIBLE);
                mSidebar.findViewById(R.id.sidebar_right_shadow).setVisibility(View.GONE);
            }
            updateDimColor(mSidebar, left);
            mPopupLayer.addPopupChild(mSidebar);
            mSidebar.updateAdapterData(conf.mDefaultPageId, entries);

            mScrollAnimationListener = null;
        } else if (openLikeNotificationBar) {
            result = mNotificationLikeOpener;

            if (left) {
                mSidebar.findViewById(R.id.sidebar_right_shadow).setVisibility(View.VISIBLE);
                mSidebar.findViewById(R.id.sidebar_left_shadow).setVisibility(View.GONE);
            } else {
                mSidebar.findViewById(R.id.sidebar_right_shadow).setVisibility(View.GONE);
                mSidebar.findViewById(R.id.sidebar_left_shadow).setVisibility(View.VISIBLE);
            }

            int w = mWindowManager.getDefaultDisplay().getWidth();
            int h = mWindowManager.getDefaultDisplay().getHeight();
            int sw = Math.min(w, h);

            mNotificationLikeOpener.setTargetView(mSidebar, sidebarWidth, sw, left);
            mSidebar.updateAdapterData(conf.mDefaultPageId, entries);
            mPopupLayer.addPopupChild(mSidebar);
        } else {
            mSidebar.setTranslationX(0);
            mPopupLayer.addPopupChild(mSidebar);
        }

        return result;
    }

    protected void updateNotificationStatus() {
        Log.d(TAG, "starting foreground");
        if (mSuspended) {
            startForeground(ONGOING_NOTIFICATION_ID, createSuspendedNotification());
        } else {
            stopForeground(false);
            startForeground(ONGOING_NOTIFICATION_ID, createOperatingNormallyNotification());
        }
    }

    private HotspotItem getDefaultConfiguration() {
        return mHotspotItems.isEmpty() ? null : mHotspotItems.get(0);
    }

    float updateDimColor(View scrollView, boolean left) {
        if (scrollView != null) {
            int mTargetWidth = scrollView.getWidth();
            float factor;
            int scroll = (int) -scrollView.getTranslationX();

            float openedPercentage;
            if (left) {
                scroll = (int) (scroll + (0 * getResources().getDisplayMetrics().density));
                factor = scroll / (float) mTargetWidth;
                openedPercentage = 1 - factor;
            } else {
                scroll = (int) (scroll - (0 * getResources().getDisplayMetrics().density));
                factor = (mTargetWidth - scroll) / (float) mTargetWidth;
                openedPercentage = 1 - (factor - 1);
            }
            mPopupLayer.setDimLayerAlpha(1);
            return openedPercentage;
        }
        return 0;
    }

    Notification createSuspendedNotification() {
        Intent resultIntent = new Intent(ACTION_UNSUSPEND);
        PendingIntent bc = PendingIntent.getBroadcast(this, 0, resultIntent, 0);

        return createNotification(bc, R.string.notification_suspended, R.drawable.appsi_notification_icon_alert)
                .build();
    }

    Notification createOperatingNormallyNotification() {

        Intent resultIntent = new Intent(this, MainActivity.class);
        PendingIntent resultPendingIntent = createPendingIntentWithBackstack(resultIntent);

        boolean minimalNotification = mPrefs.getBoolean("pref_minimal_notification", false);

        int desc = R.string.notification_description;

        int drawable = R.drawable.ic_notification;
        NotificationCompat.Builder b = createNotification(resultPendingIntent, desc, drawable);

        // When the user requested a minimal notification, do not add additional actions
        // to it. Simply show the running notification.
        if (!minimalNotification) {

            // When the normal notification must be shown, build a large text notification
            // and add the actions to it.
            NotificationCompat.BigTextStyle largeNotification = new NotificationCompat.BigTextStyle();
            largeNotification.setBigContentTitle(getString(R.string.application_name));
            largeNotification.setSummaryText(getString(R.string.notification_description));
            b.setStyle(largeNotification);

            // hotspots configuration action
            Intent hotspotIntent = new Intent(this, ManageHotspotsActivity.class);
            PendingIntent hsPendingIntent = createPendingIntentWithBackstack(hotspotIntent);
            b.addAction(R.drawable.ic_action_panels, getString(R.string.hotspots_and_pages), hsPendingIntent);

            b.setPriority(NotificationCompat.PRIORITY_MIN);
        } else {
            b.setWhen(0);
            b.setPriority(NotificationCompat.PRIORITY_MIN);
        }
        // don't show on wearables, there is no use for that
        b.setLocalOnly(true);

        return b.build();
    }

    /**
     * Creates a simple notification without any additional actions
     */
    NotificationCompat.Builder createNotification(PendingIntent action, int descriptionResourceId,
            int imageResourceId) {
        NotificationCompat.Builder b = new NotificationCompat.Builder(this);
        b.setSmallIcon(imageResourceId);
        b.setContentTitle(getString(R.string.application_name));
        b.setContentText(getString(descriptionResourceId));

        b.setContentIntent(action);
        // don't show on wearables, there is no use for that
        b.setLocalOnly(true);
        return b;
    }

    /**
     * Creates a pending intent that can be added to an action in the notification
     */
    PendingIntent createPendingIntentWithBackstack(Intent resultIntent) {
        // The stack builder object will contain an artificial back stack for the
        // started Activity.
        // This ensures that navigating backward from the Activity leads out of
        // your application to the Home screen.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        // Adds the back stack for the Intent (but not the Intent itself)
        stackBuilder.addParentStack(MainActivity.class);
        // Adds the Intent that starts the Activity to the top of the stack
        stackBuilder.addNextIntent(resultIntent);
        return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

    }

    String levelToString(int level) {
        switch (level) {
        case TRIM_MEMORY_BACKGROUND:
            return "TRIM_MEMORY_BACKGROUND";
        case TRIM_MEMORY_COMPLETE:
            return "TRIM_MEMORY_COMPLETE";
        case TRIM_MEMORY_MODERATE:
            return "TRIM_MEMORY_MODERATE";
        case TRIM_MEMORY_UI_HIDDEN:
            return "TRIM_MEMORY_UI_HIDDEN";
        case TRIM_MEMORY_RUNNING_CRITICAL:
            return "TRIM_MEMORY_RUNNING_CRITICAL";
        case TRIM_MEMORY_RUNNING_LOW:
            return "TRIM_MEMORY_RUNNING_LOW";
        case TRIM_MEMORY_RUNNING_MODERATE:
            return "TRIM_MEMORY_RUNNING_LOW";
        }
        return String.valueOf(level);
    }

    void restartAppsiService() {
        stopAppsiService();
        Intent intent = new Intent(this, Appsi.class);
        startService(intent);
    }

    void stopAppsiService() {
        Log.d(TAG, "stopAppsiService() called with: " + "");
        onSuspend();
        mCreated = true;
        mHotspotHelper.removeHotspots();
        stopForeground(true);
        mLoaderManager.doDestroy();
        stopSelf();
        removeHotspots();
        AppsiInjector.setAppsiComponent(null);
    }

    void onSuspend() {
        removeHotspots();
        mPopupLayer.onSuspend();
        Intent i = new Intent(AppsiiUtils.ACTION_SUSPEND);
        sendBroadcast(i);
    }

    void removeHotspots() {
        mHotspotHelper.removeHotspots();
    }

    /**
     * Updates Appsi's status to or from suspended.
     */
    public void setSuspended(boolean suspended) throws PermissionDeniedException {
        if (mSuspended != suspended) {
            mSuspended = suspended;
            updateNotificationStatus();
            if (suspended) {
                onSuspend();
            } else {
                onUnsuspend();
            }
        }
    }

    void onUnsuspend() throws PermissionDeniedException {
        addHotspotsIfUnlocked();
        Intent i = new Intent(AppsiiUtils.ACTION_UNSUSPEND);
        sendBroadcast(i);
    }

    void addHotspotsIfUnlocked() throws PermissionDeniedException {
        if (!isKeyguardLocked() && !mSuspended) {
            addHotspots();
        }
    }

    boolean isKeyguardLocked() {
        return mKeyguardManager.isKeyguardLocked();
    }

    void addHotspots() throws PermissionDeniedException {
        if (!mSuspended) {
            mHotspotHelper.addHotspots();
        }
    }

    /**
     * Called when loading the hotspots completes. This will remove currently
     * visible hotspots, and add new ones in case the screen is not locked
     */
    public void onHotspotsLoaded(CircularArray<HotspotItem> configurations) {
        mHotspotItems = configurations;
        mConfiguredPluginCount = configurations.size();
        updateNotificationStatus();
        mHotspotHelper.onHotspotsLoaded(configurations);
        removeHotspots();
        try {
            addHotspotsIfUnlocked();
        } catch (PermissionDeniedException e) {
            // STOPSHIP: FIXME: this must be changed!
            onPermissionDenied(e);
        }
    }

    void openSidebarFromShortcut(Intent intent) {
        //        final Uri uri = Uri.parse(intent.getStringExtra(ShortcutActivity.EXTRA_DATASET_URI));
        //        final boolean left =
        //                intent.getBooleanExtra(ShortcutActivity.EXTRA_ACTION_TO_PERFORM, false);
        //        mHandler.postDelayed(new Runnable() {
        //            @Override
        //            public void run() {
        //                openSidebar(null, uri, left ? OPEN_FLAG_LEFT : OPEN_FLAG_RIGHT, true);
        //            }
        //        }, 1);
        //
    }

    void onOpenCompleted() {
        LockableAsyncQueryHandler.unlock();
        mSidebar.setSidebarOpening(false);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        switch (key) {
        case "pref_minimal_notification":
            onNotificationOptionsChanged();
            break;
        case "pref_sidebar_dimming_level":
            int value = mPreferenceHelper.getSidebarDimLevel();
            updateDefaultDimColor(ThemingUtils.getPercentage(value));
            break;
        case "pref_icon_theme":
            // TODO: add this again
            String iconPackStringUri = sharedPreferences.getString("pref_icon_theme", null);
            Uri iconPackUri = iconPackStringUri == null ? null : Uri.parse(iconPackStringUri);
            ActiveIconPackInfo.getInstance(this).setActiveIconPackUri(iconPackUri);
            mIconCache.clearAllIcons();
            break;
        case "pref_hotspot_width":
            removeHotspots();
            try {
                addHotspotsIfUnlocked();
            } catch (PermissionDeniedException e) {
                Log.wtf(TAG, "permission denied?", e);
            }
            break;
        case "pref_hide_persistent_notification_with_hack":
            onNotificationOptionsChanged();
            break;
        case "pref_sidebar_size":
            mSidebarPercentage = mPreferenceHelper.getSidebarWidth();
            break;
        case "pref_sidebar_haptic_feedback":
            boolean mVibrate = mPreferenceHelper.getHotspotsHapticFeedbackEnabled();
            mHotspotHelper.setVibrate(mVibrate);
            break;
        }
    }

    private void onNotificationOptionsChanged() {
        updateNotificationStatus();
    }

    @Override
    public void onPopupLayerForceClosed() {
        mSidebarVisible = false;
    }

    @Override
    public void opPopupLayerHidden() throws PermissionDeniedException {
        addHotspotsIfUnlocked();
    }

    @Override
    public void opPopupLayerShown() {
        removeHotspots();
    }

    @Override
    public SwipeListener openSidebar(HotspotItem conf, CircularArray<HotspotPageEntry> entries, int flags) {
        return openSidebar(conf, entries, flags, true /* animate */);
    }

    @Override
    public boolean canShowSidebar(HotspotItem configuration) {
        return true;
    }

    @Override
    public void cancelVisualHints() {
    }

    @Override
    public void startActivity(Intent intent) {
        onCloseSidebar();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        super.startActivity(intent);
    }

    @Override
    public void startActivity(Intent intent, Bundle options) {
        onCloseSidebar();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        super.startActivity(intent, options);
    }

    @Override
    public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues,
            int extraFlags) throws IntentSender.SendIntentException {
        closeAfterAppWidgetAction();
        super.startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags);
    }

    private void closeAfterAppWidgetAction() {
        mSidebar.showCloseOverlay(new CloseCallbackImpl());
    }

    @Override
    public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues,
            int extraFlags, Bundle options) throws IntentSender.SendIntentException {
        closeAfterAppWidgetAction();
        super.startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, options);
    }

    /**
     * There seems to be a problem unregistering receivers sometimes.
     * This is definitely a bug in Appsi. For now work around it by
     * catching the exception to make sure Appsi won't crash.
     */
    @Override
    public void unregisterReceiver(BroadcastReceiver receiver) {
        try {
            super.unregisterReceiver(receiver);
        } catch (Exception e) {
            Log.wtf("Appsi", "error unregistering receiver", e);
        }
    }

    @Override
    public void onCloseCancelled() {
        mHandler.removeMessages(MESSAGE_CLOSE_SIDEBAR);
    }

    void onPermissionDenied(PermissionDeniedException e) {
        Log.d(TAG, "No permission", e);
        mPermissionUtils.showPermissionNotification(this, 1001, android.Manifest.permission.SYSTEM_ALERT_WINDOW, 0);
        stopAppsiService();

    }

    protected void initializeDagger(Context themedContext) {
        mApplicationComponent = DaggerAppsiComponent.builder().appsiModule(new AppsiModule(this, themedContext))
                .build();
        AppsiInjector.setAppsiComponent(mApplicationComponent);

        //        mSidebar = AppsiInjector.provideSidebar();
        //        mPopupLayer = AppsiInjector.providePopupLayer();
        //        mHotspotHelper = AppsiInjector.provideHotspotHelper();
        AppsiInjector.inject(this);
    }

    static class HotspotLoader extends AsyncTask<Void, Void, CircularArray<HotspotItem>> {

        private final WeakReference<Appsi> mContext;

        public HotspotLoader(Appsi c) {
            mContext = new WeakReference<>(c);
        }

        @Override
        protected CircularArray<HotspotItem> doInBackground(Void... params) {
            Context context = mContext.get();
            if (context == null)
                return null;
            return com.appsimobile.appsii.module.HotspotLoader.loadHotspots(context);
        }

        @Override
        protected void onPostExecute(CircularArray<HotspotItem> result) {
            Appsi appsi = mContext.get();
            if (appsi != null) {
                appsi.onHotspotsLoaded(result);
            }
        }
    }

    class NotificationLikeOpener implements SidebarHotspot.SwipeListener {

        final int mVelocityTreshold;

        final ScrollerCompat mScrollerCompat;

        int mTargetWidth;

        int mScreenWidth;

        boolean mLeft;

        long mLastLocationUpdate;

        boolean mRemoveAfterScrollEnd;

        //        Handler mHandler;

        WeakReference<View> mTargetView;

        private final Runnable mUpdatePositionRunnable = new Runnable() {
            @Override
            public void run() {
                boolean running = mScrollerCompat.computeScrollOffset();
                int x = mScrollerCompat.getCurrX();
                View targetView = mTargetView.get();

                mScrollerCompat.getFinalX();

                if (targetView != null) {
                    targetView.setTranslationX(-x);
                    updateDimColor();
                    if (running) {
                        targetView.postOnAnimation(this);
                    } else {
                        targetView.setTranslationX(-mScrollerCompat.getFinalX());
                        if (mRemoveAfterScrollEnd) {
                            mPopupLayer.removePopupChild(mSidebar);
                        } else {
                            onScrollEnded(targetView);
                        }
                    }
                }
            }
        };

        public NotificationLikeOpener(Context context) {
            //            mHandler = new Handler();
            mVelocityTreshold = (int) (context.getResources().getDisplayMetrics().density * 125);
            mScrollerCompat = ScrollerCompat.create(context, new DecelerateInterpolator());

        }

        @Override
        public void setSwipeLocation(SidebarHotspot hotspot, int localX, int localY, int screenX, int screenY) {
            mRemoveAfterScrollEnd = false;

            // position the targetview according to the drag
            View targetView = mTargetView.get();
            int delta = mTargetWidth - mScreenWidth;
            if (targetView != null) {
                int xValue = localX > mTargetWidth ? mTargetWidth : localX;
                if (mLeft) {
                    targetView.setTranslationX(-(mScreenWidth - xValue + delta));
                } else {
                    int x = -xValue - mTargetWidth;
                    if (x > 0) {
                        x = 0;
                    }
                    targetView.setTranslationX(-x);
                }
            }
            updateDimColor();
        }

        float updateDimColor() {
            View view = mTargetView.get();
            if (view != null) {
                return Appsi.this.updateDimColor(view, mLeft);

            }
            return 0;
        }

        @Override
        public void onSwipeEnd(SidebarHotspot hotspot, int screenX, int screenY, boolean cancelled,
                VelocityTracker velocityTracker) {
            // snap to target or close
            // fade out drag handle
            View targetView = mTargetView.get();
            velocityTracker.computeCurrentVelocity(1000);
            float xVelocity = velocityTracker.getXVelocity();
            float pct = updateDimColor();

            boolean isOpened = false;
            if (targetView != null) {
                if (mLeft) {
                    int threshold = pct >= .2f ? -mVelocityTreshold : mVelocityTreshold;
                    if (xVelocity > threshold) {
                        mScrollerCompat.startScroll((int) -targetView.getTranslationX(), 0,
                                (int) targetView.getTranslationX(), 0);
                        isOpened = true;
                        // open
                    } else {
                        mScrollerCompat.startScroll((int) -targetView.getTranslationX(), 0,
                                (int) (mTargetWidth + targetView.getTranslationX()), 0, 100);
                        // close
                    }

                } else {
                    int threshold = pct >= .2f ? mVelocityTreshold : -mVelocityTreshold;
                    if (xVelocity < threshold) {
                        mScrollerCompat.startScroll((int) -targetView.getTranslationX(), 0,
                                (int) targetView.getTranslationX(), 0);
                        isOpened = true;
                        // open
                    } else {
                        mScrollerCompat.startScroll((int) -targetView.getTranslationX(), 0,
                                (int) (-mTargetWidth + targetView.getTranslationX()), 0, 100);
                        // close
                    }
                }

                mRemoveAfterScrollEnd = !isOpened;
                targetView.postOnAnimation(mUpdatePositionRunnable);
            }
        }

        void onScrollEnded(View targetView) {
            if (targetView.getTranslationX() == 0) {
                onOpenCompleted();
            }
        }

        public void setTargetView(View v, int targetWidth, int minScreenWidth, boolean left) {
            mTargetView = new WeakReference<>(v);
            mTargetWidth = targetWidth;
            mScreenWidth = minScreenWidth;
            mLeft = left;
            mLastLocationUpdate = System.currentTimeMillis();
            if (left) {
                v.setTranslationX(-targetWidth);
            } else {
                v.setTranslationX(targetWidth);
            }
            updateDimColor();
            mLastLocationUpdate = System.currentTimeMillis();
        }

        public void cancel() {
            mScrollerCompat.abortAnimation();
        }

    }

    class LocalReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_LOCAL_OPEN_SIDEBAR_FROM_SHORTCUT.equals(action)) {
                openSidebarFromShortcut(intent);
            }
        }

        void register(Context context) {
            IntentFilter reloadFilter = new IntentFilter(ACTION_LOCAL_OPEN_SIDEBAR_FROM_SHORTCUT);
            LocalBroadcastManager mgr = LocalBroadcastManager.getInstance(context);
            mgr.registerReceiver(mLocalActionsReceiver, reloadFilter);

        }
    }

    class AppsiStatusBroadcastReceiver extends BroadcastReceiver {

        public void register(Context appsi) {
            IntentFilter isRunningIntentFilter = new IntentFilter();
            isRunningIntentFilter.addAction(ACTION_STOP_APPSI);
            isRunningIntentFilter.addAction(ACTION_RESTART_APPSI);
            isRunningIntentFilter.addAction(ACTION_ORDERED_BROADCAST_RUNNING);
            appsi.registerReceiver(this, isRunningIntentFilter);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_ORDERED_BROADCAST_RUNNING.equals(action)) {
                if (mSuspended) {
                    setResultCode(RESULT_RUNNING_STATUS_SUSPENDED);
                } else {
                    setResultCode(RESULT_RUNNING_STATUS_ENABLED);
                }
            } else if (ACTION_RESTART_APPSI.equals(action)) {
                restartAppsiService();
            } else if (ACTION_STOP_APPSI.equals(action)) {
                stopAppsiService();
            }
        }
    }

    class ExternalEventsListener extends BroadcastReceiver {

        void register(Context context) {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(Intent.ACTION_USER_PRESENT);
            intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
            intentFilter.addAction(Intent.ACTION_SCREEN_ON);
            intentFilter.addAction(ACTION_CLOSE_SIDEBAR);
            intentFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            addApi8Features(intentFilter);

            addApi17Features(intentFilter);
            context.registerReceiver(this, intentFilter);
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        private void addApi8Features(IntentFilter appActionFilter) {
            appActionFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
            appActionFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        }

        private void addApi17Features(IntentFilter intentFilter) {
            intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
            intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                onReceiveImpl(context, intent);
            } catch (PermissionDeniedException e) {
                onPermissionDenied(e);
            }
        }

        public void onReceiveImpl(Context context, Intent intent) throws PermissionDeniedException {
            String action = intent.getAction();
            switch (action) {
            case ACTION_CLOSE_SIDEBAR:
                onCloseSidebar();
                addHotspotsIfUnlocked();
                break;
            case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
                AppStatusReceiver.broadcastRefreshAllApps(context);
                break;
            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
                AppStatusReceiver.broadcastRefreshAllApps(context);
                break;
            case Intent.ACTION_USER_PRESENT:
                // show our hotspot
                addHotspots();
                break;
            case Intent.ACTION_DREAMING_STARTED:
                removeHotspots();
                break;
            case Intent.ACTION_DREAMING_STOPPED:
                addHotspotsIfUnlocked();
                break;
            case Intent.ACTION_SCREEN_ON:
                // show our hotspot in case !mKeyguardManager.isKeyguardLocked()
                // clear previous animations because they can interfere
                mSidebar.clearAnimation();
                View v = mSidebar.findViewById(R.id.sidebar_container);
                Animation a = v.getAnimation();
                if (a != null) {
                    a.setAnimationListener(null);
                }
                v.clearAnimation();
                updateNotificationStatus();

                addHotspotsIfUnlocked();
                break;
            case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
                onCloseSidebar();
                break;
            case Intent.ACTION_SCREEN_OFF:
                onCloseSidebar();
                // remove our hotspot
                removeHotspots();
                stopForeground(true);
                break;
            }
        }
    }

    class CloseCallbackImpl implements CloseCallback {

        @Override
        public void close() {
            onCloseSidebar();
        }

        @Override
        public void closeDelayed(int delayMillis) {
            mHandler.sendEmptyMessageDelayed(MESSAGE_CLOSE_SIDEBAR, delayMillis);
        }
    }

}