@Override
  public void onActivityStopped(Activity activity) {
    // Remove from the current list of running activities
    status.remove(activity.toString());

    // If there are no running activities, the app is backgrounded
    // In this scenario, log the event to Mixpanel
    if (status.isEmpty()) {
      // Send the session tracking information to Mixpanel
      mixpanelCallbacks.track("$app_open");

      // Mark the current session as inactive so we properly start a new one
      mixpanelCallbacks.unregisterSuperProperty("Session");

      // Force all queued Mixpanel data to be sent to Mixpanel
      mixpanelCallbacks.flush();
    }

    Log.d("Current Activities", "onStop() " + status.toString());
  }
  public void testHTTPFailures() {
    final List<Object> flushResults = new ArrayList<Object>();
    final BlockingQueue<String> performRequestCalls = new LinkedBlockingQueue<String>();

    final ServerMessage mockPoster =
        new ServerMessage() {
          @Override
          public byte[] performRequest(String endpointUrl, List<NameValuePair> nameValuePairs)
              throws IOException {
            if (null == nameValuePairs) {
              assertEquals(
                  "DECIDE ENDPOINT?version=1&lib=android&token=Test+Message+Queuing&distinct_id=new+person",
                  endpointUrl);
              return TestUtils.bytes("{}");
            }

            Object obj = flushResults.remove(0);
            try {
              assertEquals(nameValuePairs.get(0).getName(), "data");
              final String jsonData = Base64Coder.decodeString(nameValuePairs.get(0).getValue());
              JSONArray msg = new JSONArray(jsonData);
              JSONObject event = msg.getJSONObject(0);
              performRequestCalls.put(event.getString("event"));

              if (obj instanceof IOException) {
                throw (IOException) obj;
              } else if (obj instanceof MalformedURLException) {
                throw (MalformedURLException) obj;
              }
            } catch (JSONException e) {
              throw new RuntimeException("Malformed data passed to test mock", e);
            } catch (InterruptedException e) {
              throw new RuntimeException(
                  "Could not write message to reporting queue for tests.", e);
            }
            return (byte[]) obj;
          }
        };

    final MPConfig config =
        new MPConfig(new Bundle()) {
          public String getDecideEndpoint() {
            return "DECIDE ENDPOINT";
          }

          public String getEventsEndpoint() {
            return "EVENTS ENDPOINT";
          }

          public boolean getDisableFallback() {
            return false;
          }
        };

    final List<String> cleanupCalls = new ArrayList<String>();
    final MPDbAdapter mockAdapter =
        new MPDbAdapter(getContext()) {
          @Override
          public void cleanupEvents(String last_id, Table table) {
            cleanupCalls.add("called");
            super.cleanupEvents(last_id, table);
          }
        };

    final AnalyticsMessages listener =
        new AnalyticsMessages(getContext()) {
          @Override
          protected MPDbAdapter makeDbAdapter(Context context) {
            return mockAdapter;
          }

          @Override
          protected ServerMessage getPoster() {
            return mockPoster;
          }

          @Override
          protected MPConfig getConfig(Context context) {
            return config;
          }
        };

    MixpanelAPI metrics =
        new TestUtils.CleanMixpanelAPI(getContext(), mMockPreferences, "Test Message Queuing") {
          @Override
          protected AnalyticsMessages getAnalyticsMessages() {
            return listener;
          }
        };

    try {
      // Basic succeed on first, non-fallback url
      cleanupCalls.clear();
      flushResults.add(TestUtils.bytes("1\n"));
      metrics.track("Should Succeed", null);
      metrics.flush();
      Thread.sleep(500);
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(null, performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(1, cleanupCalls.size());

      // Fallback test--first URL throws IOException
      cleanupCalls.clear();
      flushResults.add(new IOException());
      flushResults.add(TestUtils.bytes("1\n"));
      metrics.track("Should Succeed", null);
      metrics.flush();
      Thread.sleep(500);
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(1, cleanupCalls.size());

      // Two IOExceptions -- assume temporary network failure, no cleanup should happen until
      // second flush
      cleanupCalls.clear();
      flushResults.add(new IOException());
      flushResults.add(new IOException());
      flushResults.add(TestUtils.bytes("1\n"));
      metrics.track("Should Succeed", null);
      metrics.flush();
      Thread.sleep(500);
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(0, cleanupCalls.size());
      metrics.flush();
      Thread.sleep(500);
      assertEquals("Should Succeed", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(null, performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(1, cleanupCalls.size());

      // MalformedURLException -- should dump the events since this will probably never succeed
      cleanupCalls.clear();
      flushResults.add(new MalformedURLException());
      metrics.track("Should Fail", null);
      metrics.flush();
      Thread.sleep(500);
      assertEquals("Should Fail", performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(null, performRequestCalls.poll(2, TimeUnit.SECONDS));
      assertEquals(1, cleanupCalls.size());
    } catch (InterruptedException e) {
      throw new RuntimeException("Test was interrupted.");
    }
  }
  public void testMessageQueuing() {
    final BlockingQueue<String> messages = new LinkedBlockingQueue<String>();
    final SynchronizedReference<Boolean> okToDecide = new SynchronizedReference<Boolean>();
    okToDecide.set(false);

    final MPDbAdapter mockAdapter =
        new MPDbAdapter(getContext()) {
          @Override
          public int addJSON(JSONObject message, MPDbAdapter.Table table) {
            try {
              messages.put("TABLE " + table.getName());
              messages.put(message.toString());
            } catch (InterruptedException e) {
              throw new RuntimeException(e);
            }

            return super.addJSON(message, table);
          }
        };
    mockAdapter.cleanupEvents(Long.MAX_VALUE, MPDbAdapter.Table.EVENTS);
    mockAdapter.cleanupEvents(Long.MAX_VALUE, MPDbAdapter.Table.PEOPLE);

    final ServerMessage mockPoster =
        new ServerMessage() {
          @Override
          public byte[] performRequest(String endpointUrl, List<NameValuePair> nameValuePairs) {
            final boolean decideIsOk = okToDecide.get();
            if (null == nameValuePairs) {
              if (decideIsOk) {
                assertEquals(
                    "DECIDE_ENDPOINT?version=1&lib=android&token=Test+Message+Queuing&distinct_id=new+person",
                    endpointUrl);
              } else {
                fail(
                    "User is unidentified, we shouldn't be checking decide. (URL WAS "
                        + endpointUrl
                        + ")");
              }
              return TestUtils.bytes("{}");
            }

            assertEquals(nameValuePairs.get(0).getName(), "data");
            final String decoded = Base64Coder.decodeString(nameValuePairs.get(0).getValue());

            try {
              messages.put("SENT FLUSH " + endpointUrl);
              messages.put(decoded);
            } catch (InterruptedException e) {
              throw new RuntimeException(e);
            }

            return TestUtils.bytes("1\n");
          }
        };

    final MPConfig mockConfig =
        new MPConfig(new Bundle()) {
          @Override
          public int getFlushInterval() {
            return -1;
          }

          @Override
          public int getBulkUploadLimit() {
            return 40;
          }

          @Override
          public String getEventsEndpoint() {
            return "EVENTS_ENDPOINT";
          }

          @Override
          public String getPeopleEndpoint() {
            return "PEOPLE_ENDPOINT";
          }

          @Override
          public String getDecideEndpoint() {
            return "DECIDE_ENDPOINT";
          }
        };

    final AnalyticsMessages listener =
        new AnalyticsMessages(getContext()) {
          @Override
          protected MPDbAdapter makeDbAdapter(Context context) {
            return mockAdapter;
          }

          @Override
          protected MPConfig getConfig(Context context) {
            return mockConfig;
          }

          @Override
          protected ServerMessage getPoster() {
            return mockPoster;
          }
        };

    MixpanelAPI metrics =
        new TestUtils.CleanMixpanelAPI(getContext(), mMockPreferences, "Test Message Queuing") {
          @Override
          protected AnalyticsMessages getAnalyticsMessages() {
            return listener;
          }
        };

    // Test filling up the message queue
    for (int i = 0; i < mockConfig.getBulkUploadLimit() - 1; i++) {
      metrics.track("frequent event", null);
    }

    metrics.track("final event", null);
    String expectedJSONMessage = "<No message actually received>";

    try {
      for (int i = 0; i < mockConfig.getBulkUploadLimit() - 1; i++) {
        String messageTable = messages.poll(1, TimeUnit.SECONDS);
        assertEquals("TABLE " + MPDbAdapter.Table.EVENTS.getName(), messageTable);

        expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
        JSONObject message = new JSONObject(expectedJSONMessage);
        assertEquals("frequent event", message.getString("event"));
      }

      String messageTable = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("TABLE " + MPDbAdapter.Table.EVENTS.getName(), messageTable);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONObject message = new JSONObject(expectedJSONMessage);
      assertEquals("final event", message.getString("event"));

      String messageFlush = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("SENT FLUSH EVENTS_ENDPOINT", messageFlush);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONArray bigFlush = new JSONArray(expectedJSONMessage);
      assertEquals(mockConfig.getBulkUploadLimit(), bigFlush.length());

      metrics.track("next wave", null);
      metrics.flush();

      String nextWaveTable = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("TABLE " + MPDbAdapter.Table.EVENTS.getName(), nextWaveTable);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONObject nextWaveMessage = new JSONObject(expectedJSONMessage);
      assertEquals("next wave", nextWaveMessage.getString("event"));

      String manualFlush = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("SENT FLUSH EVENTS_ENDPOINT", manualFlush);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONArray nextWave = new JSONArray(expectedJSONMessage);
      assertEquals(1, nextWave.length());

      JSONObject nextWaveEvent = nextWave.getJSONObject(0);
      assertEquals("next wave", nextWaveEvent.getString("event"));

      okToDecide.set(true);
      metrics.getPeople().identify("new person");
      metrics.getPeople().set("prop", "yup");
      metrics.flush();

      String peopleTable = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("TABLE " + MPDbAdapter.Table.PEOPLE.getName(), peopleTable);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONObject peopleMessage = new JSONObject(expectedJSONMessage);

      assertEquals("new person", peopleMessage.getString("$distinct_id"));
      assertEquals("yup", peopleMessage.getJSONObject("$set").getString("prop"));

      String peopleFlush = messages.poll(1, TimeUnit.SECONDS);
      assertEquals("SENT FLUSH PEOPLE_ENDPOINT", peopleFlush);

      expectedJSONMessage = messages.poll(1, TimeUnit.SECONDS);
      JSONArray peopleSent = new JSONArray(expectedJSONMessage);
      assertEquals(1, peopleSent.length());

    } catch (InterruptedException e) {
      fail("Expected a log message about mixpanel communication but did not recieve it.");
    } catch (JSONException e) {
      fail(
          "Expected a JSON object message and got something silly instead: " + expectedJSONMessage);
    }
  }