/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.stetho.inspector.elements.android; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.os.Build; import android.os.Bundle; import android.os.Looper; import com.facebook.stetho.common.Util; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.NotThreadSafe; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Tracks which {@link Activity} instances have been created and not yet destroyed in creation * order for use by Stetho features. Note that automatic tracking is not available for * all versions of Android but it is possible to manually track activities using the {@link #add} * and {@link #remove} methods exposed below. Be aware that this is an easy opportunity to * cause serious memory leaks in your application however. Use with caution. * <p/> * Most callers can and should ignore this class, though it is necessary if you are implementing * Activity tracking pre-ICS. */ @NotThreadSafe public final class ActivityTracker { private static final ActivityTracker sInstance = new ActivityTracker(); /** * Use {@link WeakReference} here to silence a false positive from LeakCanary: * https://github.com/facebook/stetho/issues/319#issuecomment-285699813 */ @GuardedBy("Looper.getMainLooper()") private final ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>(); private final List<WeakReference<Activity>> mActivitiesUnmodifiable = Collections.unmodifiableList(mActivities); private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); @Nullable private AutomaticTracker mAutomaticTracker; public static ActivityTracker get() { return sInstance; } public void registerListener(Listener listener) { mListeners.add(listener); } public void unregisterListener(Listener listener) { mListeners.remove(listener); } /** * Start automatic tracking if we are running on ICS+. * * @return Automatic tracking has been started. No need to manually invoke {@link #add} or * {@link #remove} methods. */ public boolean beginTrackingIfPossible(Application application) { if (mAutomaticTracker == null) { AutomaticTracker automaticTracker = AutomaticTracker.newInstanceIfPossible(application, this /* tracker */); if (automaticTracker != null) { automaticTracker.register(); mAutomaticTracker = automaticTracker; return true; } } return false; } public boolean endTracking() { if (mAutomaticTracker != null) { mAutomaticTracker.unregister(); mAutomaticTracker = null; return true; } return false; } public void add(Activity activity) { Util.throwIfNull(activity); Util.throwIfNot(Looper.myLooper() == Looper.getMainLooper()); mActivities.add(new WeakReference<>(activity)); for (Listener listener : mListeners) { listener.onActivityAdded(activity); } } public void remove(Activity activity) { Util.throwIfNull(activity); Util.throwIfNot(Looper.myLooper() == Looper.getMainLooper()); if (removeFromWeakList(mActivities, activity)) { for (Listener listener : mListeners) { listener.onActivityRemoved(activity); } } } private static <T> boolean removeFromWeakList(ArrayList<WeakReference<T>> haystack, T needle) { for (int i = 0, N = haystack.size(); i < N; i++) { T hay = haystack.get(i).get(); if (hay == needle) { haystack.remove(i); return true; } } return false; } public List<WeakReference<Activity>> getActivitiesView() { return mActivitiesUnmodifiable; } @Nullable public Activity tryGetTopActivity() { if (mActivitiesUnmodifiable.isEmpty()) { return null; } for (int i = mActivitiesUnmodifiable.size() - 1; i >= 0; i--) { Activity activity = mActivitiesUnmodifiable.get(i).get(); if (activity != null) { return activity; } } return null; } public interface Listener { public void onActivityAdded(Activity activity); public void onActivityRemoved(Activity activity); } private static abstract class AutomaticTracker { @Nullable public static AutomaticTracker newInstanceIfPossible( Application application, ActivityTracker tracker) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { return new AutomaticTrackerICSAndBeyond(application, tracker); } else { return null; } } public abstract void register(); public abstract void unregister(); @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private static class AutomaticTrackerICSAndBeyond extends AutomaticTracker { private final Application mApplication; private final ActivityTracker mTracker; public AutomaticTrackerICSAndBeyond(Application application, ActivityTracker tracker) { mApplication = application; mTracker = tracker; } public void register() { mApplication.registerActivityLifecycleCallbacks(mLifecycleCallbacks); } public void unregister() { mApplication.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks); } private final Application.ActivityLifecycleCallbacks mLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { mTracker.add(activity); } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { mTracker.remove(activity); } }; } } }