text: /*
   * Copyright (C) 2018 Square, Inc.
   *
   * 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.squareup.leakcanary;
  
  import android.app.Activity;
  import android.app.Application;
  import android.app.Dialog;
  import android.app.Fragment;
  import android.os.MessageQueue;
  import androidx.annotation.NonNull;
  import android.view.View;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * A set of default {@link Reachability.Inspector}s that knows about common AOSP and library
   * classes.
   *
   * These are heuristics based on our experience and knownledge of AOSP and various library
   * internals. We only make a reachability decision if we're reasonably sure such reachability is
   * unlikely to be the result of a programmer mistake.
   *
   * For example, no matter how many mistakes we make in our code, the value of Activity.mDestroy
   * will not be influenced by those mistakes.
   */
  public enum AndroidReachabilityInspectors {
  
    VIEW(ViewInspector.class),
  
    ACTIVITY(ActivityInspector.class),
  
    DIALOG(DialogInspector.class),
  
    APPLICATION(ApplicationInspector.class),
  
    FRAGMENT(FragmentInspector.class),
  
    SUPPORT_FRAGMENT(SupportFragmentInspector.class),
  
    MESSAGE_QUEUE(MessageQueueInspector.class),
  
    MORTAR_PRESENTER(MortarPresenterInspector.class),
  
    VIEW_ROOT_IMPL(ViewImplInspector.class),
  
    MAIN_THEAD(MainThreadInspector.class),
  
    WINDOW(WindowInspector.class),
  
    //
    ;
  
    private final Class<? extends Reachability.Inspector> inspectorClass;
  
    AndroidReachabilityInspectors(Class<? extends Reachability.Inspector> inspectorClass) {
      this.inspectorClass = inspectorClass;
    }
  
    public static @NonNull
    List<Class<? extends Reachability.Inspector>> defaultAndroidInspectors() {
      List<Class<? extends Reachability.Inspector>> inspectorClasses = new ArrayList<>();
      for (AndroidReachabilityInspectors enumValue : AndroidReachabilityInspectors.values()) {
        inspectorClasses.add(enumValue.inspectorClass);
      }
      return inspectorClasses;
    }
  
    public static class ViewInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(View.class)) {
          return Reachability.UNKNOWN;
        }
        String mAttachInfo = element.getFieldReferenceValue("mAttachInfo");
        if (mAttachInfo == null) {
          return Reachability.UNKNOWN;
        }
        return mAttachInfo.equals("null") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class ActivityInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(Activity.class)) {
          return Reachability.UNKNOWN;
        }
        String mDestroyed = element.getFieldReferenceValue("mDestroyed");
        if (mDestroyed == null) {
          return Reachability.UNKNOWN;
        }
        return mDestroyed.equals("true") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class DialogInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(Dialog.class)) {
          return Reachability.UNKNOWN;
        }
        String mDecor = element.getFieldReferenceValue("mDecor");
        if (mDecor == null) {
          return Reachability.UNKNOWN;
        }
        return mDecor.equals("null") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class ApplicationInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (element.isInstanceOf(Application.class)) {
          return Reachability.REACHABLE;
        }
        return Reachability.UNKNOWN;
      }
    }
  
    public static class FragmentInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(Fragment.class)) {
          return Reachability.UNKNOWN;
        }
        String mDetached = element.getFieldReferenceValue("mDetached");
        if (mDetached == null) {
          return Reachability.UNKNOWN;
        }
        return mDetached.equals("true") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class SupportFragmentInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf("androidx.fragment.app.Fragment")) {
          return Reachability.UNKNOWN;
        }
        String mDetached = element.getFieldReferenceValue("mDetached");
        if (mDetached == null) {
          return Reachability.UNKNOWN;
        }
        return mDetached.equals("true") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class MessageQueueInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(MessageQueue.class)) {
          return Reachability.UNKNOWN;
        }
        String mQuitting = element.getFieldReferenceValue("mQuitting");
        // If the queue is not quitting, maybe it should actually have been, we don't know.
        // However, if it's quitting, it is very likely that's not a bug.
        if ("true".equals(mQuitting)) {
          return Reachability.UNREACHABLE;
        }
        return Reachability.UNKNOWN;
      }
    }
  
    public static class MortarPresenterInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf("mortar.Presenter")) {
          return Reachability.UNKNOWN;
        }
        String view = element.getFieldReferenceValue("view");
  
        // Bugs in view code tends to cause Mortar presenters to still have a view when they actually
        // should be a unreachable, so in that case we don't know their reachability status. However,
        // when the view is null, we're pretty sure they should be unreachable.
        if ("null".equals(view)) {
          return Reachability.UNREACHABLE;
        }
        return Reachability.UNKNOWN;
      }
    }
  
    public static class ViewImplInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf("android.view.ViewRootImpl")) {
          return Reachability.UNKNOWN;
        }
        String mView = element.getFieldReferenceValue("mView");
        if (mView == null) {
          return Reachability.UNKNOWN;
        }
        return mView.equals("null") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
    public static class MainThreadInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf(Thread.class)) {
          return Reachability.UNKNOWN;
        }
        String name = element.getFieldReferenceValue("name");
        if ("main".equals(name)) {
          return Reachability.REACHABLE;
        }
        return Reachability.UNKNOWN;
      }
    }
  
    public static class WindowInspector implements Reachability.Inspector {
      @Override
      public @NonNull
      Reachability expectedReachability(@NonNull LeakTraceElement element) {
        if (!element.isInstanceOf("android.view.Window")) {
          return Reachability.UNKNOWN;
        }
        String mDestroyed = element.getFieldReferenceValue("mDestroyed");
        if (mDestroyed == null) {
          return Reachability.UNKNOWN;
        }
        return mDestroyed.equals("true") ? Reachability.UNREACHABLE : Reachability.REACHABLE;
      }
    }
  
  }
  