/**
   * Run the command!
   *
   * @param args The command-line arguments
   * @return Returns a posix-style result code. 0 for no error.
   */
  private int run(String[] args) {
    // Super-early debugger wait
    for (String s : args) {
      if ("--wait-dbg".equals(s)) {
        Debug.waitForDebugger();
      }
    }

    // Default values for some command-line options
    mVerbose = 0;
    mCount = 1000;
    mSeed = 0;
    mThrottle = 0;

    // prepare for command-line processing
    mArgs = args;
    mNextArg = 0;

    // set a positive value, indicating none of the factors is provided yet
    for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
      mFactors[i] = 1.0f;
    }

    if (!processOptions()) {
      return -1;
    }

    if (!loadPackageLists()) {
      return -1;
    }

    // now set up additional data in preparation for launch
    if (mMainCategories.size() == 0) {
      mMainCategories.add(Intent.CATEGORY_LAUNCHER);
      mMainCategories.add(Intent.CATEGORY_MONKEY);
    }

    if (mVerbose > 0) {
      System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
      if (mValidPackages.size() > 0) {
        Iterator<String> it = mValidPackages.iterator();
        while (it.hasNext()) {
          System.out.println(":AllowPackage: " + it.next());
        }
      }
      if (mInvalidPackages.size() > 0) {
        Iterator<String> it = mInvalidPackages.iterator();
        while (it.hasNext()) {
          System.out.println(":DisallowPackage: " + it.next());
        }
      }
      if (mMainCategories.size() != 0) {
        Iterator<String> it = mMainCategories.iterator();
        while (it.hasNext()) {
          System.out.println(":IncludeCategory: " + it.next());
        }
      }
    }

    if (!checkInternalConfiguration()) {
      return -2;
    }

    if (!getSystemInterfaces()) {
      return -3;
    }

    if (!getMainApps()) {
      return -4;
    }

    mRandom = new SecureRandom();
    mRandom.setSeed((mSeed == 0) ? -1 : mSeed);

    if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
      // script mode, ignore other options
      mEventSource =
          new MonkeySourceScript(
              mRandom,
              mScriptFileNames.get(0),
              mThrottle,
              mRandomizeThrottle,
              mProfileWaitTime,
              mDeviceSleepTime);
      mEventSource.setVerbose(mVerbose);

      mCountEvents = false;
    } else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
      if (mSetupFileName != null) {
        mEventSource =
            new MonkeySourceRandomScript(
                mSetupFileName,
                mScriptFileNames,
                mThrottle,
                mRandomizeThrottle,
                mRandom,
                mProfileWaitTime,
                mDeviceSleepTime,
                mRandomizeScript);
        mCount++;
      } else {
        mEventSource =
            new MonkeySourceRandomScript(
                mScriptFileNames,
                mThrottle,
                mRandomizeThrottle,
                mRandom,
                mProfileWaitTime,
                mDeviceSleepTime,
                mRandomizeScript);
      }
      mEventSource.setVerbose(mVerbose);
      mCountEvents = false;
    } else if (mServerPort != -1) {
      try {
        mEventSource = new MonkeySourceNetwork(mServerPort);
      } catch (IOException e) {
        System.out.println("Error binding to network socket.");
        return -5;
      }
      mCount = Integer.MAX_VALUE;
    } else {
      // random source by default
      if (mVerbose >= 2) { // check seeding performance
        System.out.println("// Seeded: " + mSeed);
      }
      mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
      mEventSource.setVerbose(mVerbose);
      // set any of the factors that has been set
      for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
        if (mFactors[i] <= 0.0f) {
          ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
        }
      }

      // in random mode, we start with a random activity
      ((MonkeySourceRandom) mEventSource).generateActivity();
    }

    // validate source generator
    if (!mEventSource.validate()) {
      return -5;
    }

    // If we're profiling, do it immediately before/after the main monkey
    // loop
    if (mGenerateHprof) {
      signalPersistentProcesses();
    }

    mNetworkMonitor.start();
    int crashedAtCycle = runMonkeyCycles();
    mNetworkMonitor.stop();

    synchronized (this) {
      if (mRequestAnrTraces) {
        reportAnrTraces();
        mRequestAnrTraces = false;
      }
      if (mRequestAnrBugreport) {
        System.out.println("Print the anr report");
        getBugreport("anr_" + mReportProcessName + "_");
        mRequestAnrBugreport = false;
      }
      if (mRequestAppCrashBugreport) {
        getBugreport("app_crash" + mReportProcessName + "_");
        mRequestAppCrashBugreport = false;
      }
      if (mRequestDumpsysMemInfo) {
        reportDumpsysMemInfo();
        mRequestDumpsysMemInfo = false;
      }
    }

    if (mGenerateHprof) {
      signalPersistentProcesses();
      if (mVerbose > 0) {
        System.out.println("// Generated profiling reports in /data/misc");
      }
    }

    try {
      mAm.setActivityController(null);
      mNetworkMonitor.unregister(mAm);
    } catch (RemoteException e) {
      // just in case this was latent (after mCount cycles), make sure
      // we report it
      if (crashedAtCycle >= mCount) {
        crashedAtCycle = mCount - 1;
      }
    }

    // report dropped event stats
    if (mVerbose > 0) {
      System.out.print(":Dropped: keys=");
      System.out.print(mDroppedKeyEvents);
      System.out.print(" pointers=");
      System.out.print(mDroppedPointerEvents);
      System.out.print(" trackballs=");
      System.out.print(mDroppedTrackballEvents);
      System.out.print(" flips=");
      System.out.println(mDroppedFlipEvents);
    }

    // report network stats
    mNetworkMonitor.dump();

    if (crashedAtCycle < mCount - 1) {
      System.err.println(
          "** System appears to have crashed at event "
              + crashedAtCycle
              + " of "
              + mCount
              + " using seed "
              + mSeed);
      return crashedAtCycle;
    } else {
      if (mVerbose > 0) {
        System.out.println("// Monkey finished");
      }
      return 0;
    }
  }
  /**
   * Run mCount cycles and see if we hit any crashers.
   *
   * <p>TODO: Meta state on keys
   *
   * @return Returns the last cycle which executed. If the value == mCount, no errors detected.
   */
  private int runMonkeyCycles() {
    int eventCounter = 0;
    int cycleCounter = 0;

    boolean systemCrashed = false;

    // TO DO : The count should apply to each of the script file.
    while (!systemCrashed && cycleCounter < mCount) {
      synchronized (this) {
        if (mRequestProcRank) {
          reportProcRank();
          mRequestProcRank = false;
        }
        if (mRequestAnrTraces) {
          reportAnrTraces();
          mRequestAnrTraces = false;
        }
        if (mRequestAnrBugreport) {
          getBugreport("anr_" + mReportProcessName + "_");
          mRequestAnrBugreport = false;
        }
        if (mRequestAppCrashBugreport) {
          getBugreport("app_crash" + mReportProcessName + "_");
          mRequestAppCrashBugreport = false;
        }
        if (mRequestDumpsysMemInfo) {
          reportDumpsysMemInfo();
          mRequestDumpsysMemInfo = false;
        }
        if (mMonitorNativeCrashes) {
          // first time through, when eventCounter == 0, just set up
          // the watcher (ignore the error)
          if (checkNativeCrashes() && (eventCounter > 0)) {
            System.out.println("** New native crash detected.");
            if (mRequestBugreport) {
              getBugreport("native_crash_");
            }
            mAbort = mAbort || !mIgnoreNativeCrashes || mKillProcessAfterError;
          }
        }
        if (mAbort) {
          System.out.println("** Monkey aborted due to error.");
          System.out.println("Events injected: " + eventCounter);
          return eventCounter;
        }
      }

      // In this debugging mode, we never send any events. This is
      // primarily here so you can manually test the package or category
      // limits, while manually exercising the system.
      if (mSendNoEvents) {
        eventCounter++;
        cycleCounter++;
        continue;
      }

      if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
        String calendarTime = MonkeyUtils.toCalendarTime(System.currentTimeMillis());
        long systemUpTime = SystemClock.elapsedRealtime();
        System.out.println(
            "    //[calendar_time:" + calendarTime + " system_uptime:" + systemUpTime + "]");
        System.out.println("    // Sending event #" + eventCounter);
      }

      MonkeyEvent ev = mEventSource.getNextEvent();
      if (ev != null) {
        int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
        if (injectCode == MonkeyEvent.INJECT_FAIL) {
          if (ev instanceof MonkeyKeyEvent) {
            mDroppedKeyEvents++;
          } else if (ev instanceof MonkeyMotionEvent) {
            mDroppedPointerEvents++;
          } else if (ev instanceof MonkeyFlipEvent) {
            mDroppedFlipEvents++;
          }
        } else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
          systemCrashed = true;
          System.err.println("** Error: RemoteException while injecting event.");
        } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
          systemCrashed = !mIgnoreSecurityExceptions;
          if (systemCrashed) {
            System.err.println("** Error: SecurityException while injecting event.");
          }
        }

        // Don't count throttling as an event.
        if (!(ev instanceof MonkeyThrottleEvent)) {
          eventCounter++;
          if (mCountEvents) {
            cycleCounter++;
            if (mScriptLog) {
              writeScriptLog(cycleCounter);
            }
          }
        }
      } else {
        if (!mCountEvents) {
          cycleCounter++;
          if (mScriptLog) {
            writeScriptLog(cycleCounter);
          }
        } else {
          // Event Source has signaled that we have no more events to process
          break;
        }
      }
    }
    System.out.println("Events injected: " + eventCounter);
    return eventCounter;
  }