/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.linkbubble.util; import android.app.Activity; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.Log; import com.linkbubble.BuildConfig; import com.linkbubble.MainController; import com.linkbubble.ui.EntryActivity; import com.linkbubble.ui.ExpandedActivity; import com.linkbubble.ui.NotificationCloseAllActivity; import com.linkbubble.ui.NotificationCloseTabActivity; import com.linkbubble.ui.NotificationHideActivity; import com.linkbubble.ui.NotificationOpenTabActivity; import com.linkbubble.ui.NotificationUnhideActivity; import java.util.List; public class AppPoller { private static final String TAG = "AppPoller"; public interface AppPollerListener { void onAppChanged(); } private static int VERIFY_TIME = 150; private static final int ACTION_POLL_CURRENT_APP = 1; private static final int LOOP_TIME = 50; private Context mContext; private AppPollerListener mAppPollingListener; private String mCurrentAppFlatComponentName; private String mNextAppFlatComponentName; private long mNextAppFirstRunningTime = -1; private boolean mPolling = false; public AppPoller(Context context) { mContext = context; } public void setListener(AppPollerListener listener) { mAppPollingListener = listener; } public void beginAppPolling() { if (mCurrentAppFlatComponentName == null) { ActivityManager am = (ActivityManager)mContext.getSystemService(Activity.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1); if (runningTasks != null && runningTasks.size() > 0) { ComponentName componentName = runningTasks.get(0).topActivity; mCurrentAppFlatComponentName = componentName.flattenToShortString(); Log.d(TAG, "beginAppPolling() - current app:" + mCurrentAppFlatComponentName); } } mNextAppFirstRunningTime = -1; mNextAppFlatComponentName = null; if (mPolling == false) { mHandler.sendEmptyMessageDelayed(ACTION_POLL_CURRENT_APP, LOOP_TIME); } mPolling = true; } public void endAppPolling() { mHandler.removeMessages(ACTION_POLL_CURRENT_APP); mPolling = false; mCurrentAppFlatComponentName = null; Log.d(TAG, "endAppPolling()"); } // ES FileExplorer seems to employ a nasty hack whereby they start a new Activity when an app is installed/updated. // Add this equally nasty hack to ignore this one activity. Stops the Bubbles going into BubbleView mode without any input (see #179) private static final String[] IGNORE_ACTIVITIES = {"com.estrongs.android.pop/.app.InstallMonitorActivity", "com.ideashower.readitlater.pro/com.ideashower.readitlater.activity.AddActivity", BuildConfig.APPLICATION_ID + "/" + ExpandedActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + NotificationCloseAllActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + NotificationCloseTabActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + NotificationHideActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + NotificationUnhideActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + NotificationOpenTabActivity.class.getName(), BuildConfig.APPLICATION_ID + "/" + EntryActivity.class.getName()}; private boolean shouldIgnoreActivity(String flatComponentName) { for (String string : IGNORE_ACTIVITIES) { if (string.equals(flatComponentName)) { if (flatComponentName.contains(ExpandedActivity.class.getName()) == false) { Log.d(TAG, "ignore " + flatComponentName); } return true; } } return false; } private static final int sCountToCallGc = 2000; private int mCurrentLoopCount = 0; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case ACTION_POLL_CURRENT_APP: { mCurrentLoopCount++; mHandler.removeMessages(ACTION_POLL_CURRENT_APP); if (MainController.get() == null) { Log.d(TAG, "No main controller, exit"); break; } ActivityManager am = (ActivityManager)mContext.getSystemService(Activity.ACTIVITY_SERVICE); //Log.d(TAG, "Checking current tasks..."); List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1); if (runningTasks.isEmpty()) { CrashTracking.log(TAG + ": No running tasks!"); break; } ComponentName componentName = runningTasks.get(0).topActivity; String appFlatComponentName = componentName.flattenToShortString(); if (appFlatComponentName != null && mCurrentAppFlatComponentName != null && !appFlatComponentName.equals(mCurrentAppFlatComponentName)) { long currentTime = System.currentTimeMillis(); if (mNextAppFirstRunningTime == -1 || (mNextAppFlatComponentName != null && mNextAppFlatComponentName.equals(appFlatComponentName) == false)) { mNextAppFirstRunningTime = currentTime; mNextAppFlatComponentName = appFlatComponentName; Log.d(TAG, "next app to maybe be changed from " + mCurrentAppFlatComponentName + " to " + appFlatComponentName); } long timeDelta = currentTime - mNextAppFirstRunningTime; if (shouldIgnoreActivity(appFlatComponentName) == false && mNextAppFlatComponentName.equals(appFlatComponentName) && timeDelta >= VERIFY_TIME && mCurrentAppFlatComponentName.equals(appFlatComponentName) == false) { String oldApp = mCurrentAppFlatComponentName; mCurrentAppFlatComponentName = appFlatComponentName; // It's possible the current app has been set to an app we should ignore (like Pocket or ES File Explorer) // in beginAppPolling(). In that case, change mCurrentAppFlatComponentName, but don't inform the app about the // change as it involved an app we should be ignoring. if (shouldIgnoreActivity(oldApp)) { mHandler.sendEmptyMessageDelayed(ACTION_POLL_CURRENT_APP, LOOP_TIME); Log.d(TAG, "ignore app changing from " + mCurrentAppFlatComponentName + " to " + appFlatComponentName); } else { Log.d(TAG, "current app changed from " + mCurrentAppFlatComponentName + " to " + appFlatComponentName + ", triggering onAppChanged()..."); if (mAppPollingListener != null) { mAppPollingListener.onAppChanged(); } } } else { mHandler.sendEmptyMessageDelayed(ACTION_POLL_CURRENT_APP, LOOP_TIME); } } else { if (mNextAppFlatComponentName != null) { Log.d(TAG, "*** successfully ignored setting current app to " + mNextAppFlatComponentName); } mNextAppFirstRunningTime = -1; mNextAppFlatComponentName = null; mHandler.sendEmptyMessageDelayed(ACTION_POLL_CURRENT_APP, LOOP_TIME); } if (mCurrentLoopCount == sCountToCallGc) { System.gc(); mCurrentLoopCount = 0; } break; } } } }; }