private static boolean applySmoothedValues(List<AppStats> statsForApp) {
    for (AppStats appInfo : statsForApp) {
      if (appInfo.isSmoothingApplied()) {
        return true;
      }
    }

    return false;
  }
  /**
   * Parses the supplied JSON string and adds the extracted ratings to the supplied {@link AppStats}
   * object
   *
   * @param json
   * @param stats
   * @throws JSONException
   */
  static void parseRatings(String json, AppStats stats) throws JSONException {
    // Extract just the array with the values
    JSONArray values = new JSONObject(json).getJSONArray("result").getJSONArray(1).getJSONArray(0);

    // Ratings are at index 2 - 6
    stats.setRating(
        values.getInt(2), values.getInt(3), values.getInt(4), values.getInt(5), values.getInt(6));
  }
Exemple #3
0
  /**
   * Parses the supplied JSON string and adds the extracted ratings to the supplied {@link AppStats}
   * object
   *
   * @param json
   * @param stats
   * @throws JSONException
   */
  static void parseRatings(String json, AppStats stats) throws JSONException {
    // Extract just the array with the values
    JSONObject values =
        new JSONObject(json).getJSONObject("result").getJSONArray("1").getJSONObject(0);

    // Ratings are at index 2 - 6
    stats.setRating(
        values.getInt("2"),
        values.getInt("3"),
        values.getInt("4"),
        values.getInt("5"),
        values.getInt("6"));
  }
Exemple #4
0
  /**
   * Parses the supplied JSON string and adds the extracted statistics to the supplied {@link
   * AppStats} object based on the supplied statsType Not used at the moment
   *
   * @param json
   * @param stats
   * @param statsType
   * @throws JSONException
   */
  static void parseStatistics(String json, AppStats stats, int statsType) throws JSONException {
    // Extract the top level values array
    JSONObject values = new JSONObject(json).getJSONObject("result").getJSONObject("1");
    /*
     * null
     * Nested array [null, [null, Array containing historical data]]
     * null
     * null
     * null
     * Nested arrays containing summary and historical data broken down by dimension e.g.
     * Android version
     * null
     * null
     * App name
     */
    // For now we just care about todays value, later we may delve into the historical and
    // dimensioned data
    JSONArray historicalData = values.getJSONObject("1").getJSONArray("1");
    JSONObject latestData = historicalData.getJSONObject(historicalData.length() - 1);
    /*
     * null
     * Date
     * [null, value]
     */
    int latestValue = latestData.getJSONObject("2").getInt("1");

    switch (statsType) {
      case DevConsoleV2Protocol.STATS_TYPE_TOTAL_USER_INSTALLS:
        stats.setTotalDownloads(latestValue);
        break;
      case DevConsoleV2Protocol.STATS_TYPE_ACTIVE_DEVICE_INSTALLS:
        stats.setActiveInstalls(latestValue);
        break;
      default:
        break;
    }
  }
Exemple #5
0
  /**
   * Parses the supplied JSON string and builds a list of apps from it
   *
   * @param json
   * @param accountName
   * @param skipIncomplete
   * @return List of apps
   * @throws JSONException
   */
  static List<AppInfo> parseAppInfos(String json, String accountName, boolean skipIncomplete)
      throws JSONException {

    Date now = new Date();
    List<AppInfo> apps = new ArrayList<AppInfo>();
    // Extract the base array containing apps
    JSONObject result = new JSONObject(json).getJSONObject("result");
    if (DEBUG) {
      pp("result", result);
    }

    JSONArray jsonApps = result.optJSONArray("1");
    if (DEBUG) {
      pp("jsonApps", jsonApps);
    }
    if (jsonApps == null) {
      // no apps yet?
      return apps;
    }

    int numberOfApps = jsonApps.length();
    Log.d(TAG, String.format("Found %d apps in JSON", numberOfApps));
    for (int i = 0; i < numberOfApps; i++) {
      AppInfo app = new AppInfo();
      app.setAccount(accountName);
      app.setLastUpdate(now);
      // Per app:
      // 1 : { 1: package name,
      // 2 : { 1: [{1 : lang, 2: name, 3: description, 4: ??, 5: what's new}], 2 : ?? },
      // 3 : ??,
      // 4 : update history,
      // 5 : price,
      // 6 : update date,
      // 7 : state?
      // }
      // 2 : {}
      // 3 : { 1: active dnd, 2: # ratings, 3: avg rating, 4: ???, 5: total dnd }

      // arrays have changed to objects, with the index as the key
      /*
       * Per app:
       * null
       * [ APP_INFO_ARRAY
       * * null
       * * packageName
       * * Nested array with details
       * * null
       * * Nested array with version details
       * * Nested array with price details
       * * Last update Date
       * * Number [1=published, 5 = draft?]
       * ]
       * null
       * [ APP_STATS_ARRAY
       * * null,
       * * Active installs
       * * Total ratings
       * * Average rating
       * * Errors
       * * Total installs
       * ]
       */
      JSONObject jsonApp = jsonApps.getJSONObject(i);
      JSONObject jsonAppInfo = jsonApp.getJSONObject("1");
      if (DEBUG) {
        pp("jsonAppInfo", jsonAppInfo);
      }
      String packageName = jsonAppInfo.getString("1");
      // Look for "tmp.7238057230750432756094760456.235728507238057230542"
      if (packageName == null
          || (packageName.startsWith("tmp.") && Character.isDigit(packageName.charAt(4)))) {
        Log.d(TAG, String.format("Skipping draft app %d, package name=%s", i, packageName));
        continue;
        // Draft app
      }

      // Check number code and last updated date
      // Published: 1
      // Unpublished: 2
      // Draft: 5
      // Draft w/ in-app items?: 6
      // TODO figure out the rest and add don't just skip, filter, etc. Cf. #223
      int publishState = jsonAppInfo.optInt("7");
      Log.d(TAG, String.format("%s: publishState=%d", packageName, publishState));
      if (publishState != 1) {
        // Not a published app, skipping
        Log.d(
            TAG,
            String.format(
                "Skipping app %d with state != 1: package name=%s: state=%d",
                i, packageName, publishState));
        continue;
      }
      app.setPublishState(publishState);
      app.setPackageName(packageName);

      /*
       * Per app details:
       * 1: Country code
       * 2: App Name
       * 3: Description
       * 4: Promo text
       * 5: Last what's new
       */
      // skip if we can't get all the data
      // XXX should we just let this crash so we know there is a problem?
      if (!jsonAppInfo.has("2")) {
        if (skipIncomplete) {
          Log.d(
              TAG,
              String.format(
                  "Skipping app %d because no app details found: package name=%s", i, packageName));
        } else {
          Log.d(TAG, "Adding incomplete app: " + packageName);
          apps.add(app);
        }
        continue;
      }
      if (!jsonAppInfo.has("4")) {
        if (skipIncomplete) {
          Log.d(
              TAG,
              String.format(
                  "Skipping app %d because no versions info found: package name=%s",
                  i, packageName));
        } else {
          Log.d(TAG, "Adding incomplete app: " + packageName);
          apps.add(app);
        }
        continue;
      }

      JSONObject appDetails = jsonAppInfo.getJSONObject("2").getJSONArray("1").getJSONObject(0);
      if (DEBUG) {
        pp("appDetails", appDetails);
      }
      app.setName(appDetails.getString("2"));

      String description = appDetails.getString("3");
      String changelog = appDetails.optString("5");
      Long lastPlayStoreUpdate = jsonAppInfo.getJSONObject("11").getLong("1");
      AppDetails details = new AppDetails(description, changelog, lastPlayStoreUpdate);
      app.setDetails(details);

      /*
       * Per app version details:
       * null
       * null
       * packageName
       * versionNumber
       * versionName
       * null
       * Array with app icon [null,null,null,icon]
       */
      // XXX
      JSONArray appVersions = jsonAppInfo.getJSONObject("4").getJSONObject("1").optJSONArray("1");
      if (DEBUG) {
        pp("appVersions", appVersions);
      }
      if (appVersions == null) {
        if (skipIncomplete) {
          Log.d(
              TAG,
              String.format(
                  "Skipping app %d because no versions info found: package name=%s",
                  i, packageName));
        } else {
          Log.d(TAG, "Adding incomplete app: " + packageName);
          apps.add(app);
        }
        continue;
      }
      JSONObject lastAppVersionDetails =
          appVersions.getJSONObject(appVersions.length() - 1).getJSONObject("2");
      if (DEBUG) {
        pp("lastAppVersionDetails", lastAppVersionDetails);
      }
      app.setVersionName(lastAppVersionDetails.getString("4"));
      app.setIconUrl(lastAppVersionDetails.getJSONObject("6").getString("3"));

      // App stats
      /*
       * null,
       * Active installs
       * Total ratings
       * Average rating
       * Errors
       * Total installs
       */
      // XXX this index might not be correct for all apps?
      // 3 : { 1: active dnd, 2: # ratings, 3: avg rating, 4: #errors?, 5: total dnd }
      JSONObject jsonAppStats = jsonApp.optJSONObject("3");
      if (DEBUG) {
        pp("jsonAppStats", jsonAppStats);
      }
      if (jsonAppStats == null) {
        if (skipIncomplete) {
          Log.d(
              TAG,
              String.format(
                  "Skipping app %d because no stats found: package name=%s", i, packageName));
        } else {
          Log.d(TAG, "Adding incomplete app: " + packageName);
          apps.add(app);
        }
        continue;
      }
      AppStats stats = new AppStats();
      stats.setDate(now);
      if (jsonAppStats.length() < 4) {
        // no statistics (yet?) or weird format
        // TODO do we need differentiate?
        stats.setActiveInstalls(0);
        stats.setTotalDownloads(0);
        stats.setNumberOfErrors(0);
      } else {
        stats.setActiveInstalls(jsonAppStats.getInt("1"));
        stats.setTotalDownloads(jsonAppStats.getInt("5"));
        stats.setNumberOfErrors(jsonAppStats.optInt("4"));
      }
      app.setLatestStats(stats);

      apps.add(app);
    }

    return apps;
  }
  @SuppressWarnings("resource")
  public List<AppStats> readStats(InputStream in) throws ServiceException {

    List<AppStats> appStats = new ArrayList<AppStats>();

    CSVReader reader;
    try {
      // we don't own the stream, it's closed by the caller
      reader = new CSVReader(new InputStreamReader(in));

      String[] firstLine = reader.readNext();

      if (firstLine != null) {

        String[] nextLine = null;

        while ((nextLine = reader.readNext()) != null) {

          AppStats stats = new AppStats();
          stats.setPackageName(nextLine[0]);
          stats.setDate(createTimestampFormat().parse(nextLine[1]));
          stats.setTotalDownloads(Integer.parseInt(nextLine[2]));
          stats.setActiveInstalls(Integer.parseInt(nextLine[3]));
          stats.setNumberOfComments(Integer.parseInt(nextLine[4]));
          stats.setRating1(Integer.parseInt(nextLine[5]));
          stats.setRating2(Integer.parseInt(nextLine[6]));
          stats.setRating3(Integer.parseInt(nextLine[7]));
          stats.setRating4(Integer.parseInt(nextLine[8]));
          stats.setRating5(Integer.parseInt(nextLine[9]));

          if (nextLine.length > 10) {
            stats.setVersionCode(Integer.parseInt(nextLine[10]));
          }

          if (nextLine.length > 11) {
            String numErrorsStr = nextLine[11];
            stats.setNumberOfErrors(parseInt(numErrorsStr));
          }

          if (nextLine.length > 12) {
            String totalRevenueStr = nextLine[12];
            if (!TextUtils.isEmpty(totalRevenueStr)) {
              String currency = nextLine[13];
              stats.setTotalRevenue(
                  new Revenue(Revenue.Type.TOTAL, parseDouble(totalRevenueStr.trim()), currency));
            }
          }

          appStats.add(stats);
        }
      }
    } catch (FileNotFoundException e) {
      throw new ServiceException(e);
    } catch (IOException e) {
      throw new ServiceException(e);
    } catch (ParseException e) {
      throw new ServiceException(e);
    }

    return appStats;
  }
  @SuppressWarnings("resource")
  public void writeStats(String packageName, List<AppStats> stats, ZipOutputStream zip)
      throws IOException {
    zip.putNextEntry(new ZipEntry(packageName + CSV_SUFFIX));

    // we don't own the stream, it's closed by the caller
    CSVWriter writer = new CSVWriter(new OutputStreamWriter(zip));
    writer.writeNext(HEADER_LIST);

    String[] line = new String[HEADER_LIST.length];

    for (AppStats stat : stats) {

      line[0] = packageName;
      line[1] = createTimestampFormat().format(stat.getDate());
      line[2] = Integer.toString(stat.getTotalDownloads());
      line[3] = Integer.toString(stat.getActiveInstalls());
      line[4] = Integer.toString(stat.getNumberOfComments());

      line[5] = Utils.safeToString(stat.getRating1());
      line[6] = Utils.safeToString(stat.getRating2());
      line[7] = Utils.safeToString(stat.getRating3());
      line[8] = Utils.safeToString(stat.getRating4());
      line[9] = Utils.safeToString(stat.getRating5());

      line[10] = Utils.safeToString(stat.getVersionCode());

      line[11] = Utils.safeToString(stat.getNumberOfErrors());

      line[12] =
          stat.getTotalRevenue() == null
              ? ""
              : String.format(Locale.US, "%.2f", stat.getTotalRevenue().getAmount());
      line[13] = stat.getTotalRevenue() == null ? "" : stat.getTotalRevenue().getCurrencyCode();

      writer.writeNext(line);
    }
    writer.flush();
  }
  /**
   * Parses the supplied JSON string and builds a list of apps from it
   *
   * @param json
   * @param accountName
   * @return List of apps
   * @throws JSONException
   */
  static List<AppInfo> parseAppInfos(String json, String accountName) throws JSONException {

    Date now = new Date();
    List<AppInfo> apps = new ArrayList<AppInfo>();
    // Extract the base array containing apps
    JSONArray jsonApps = new JSONObject(json).getJSONArray("result").getJSONArray(1);
    if (DEBUG) {
      pp("jsonApps", jsonApps);
    }

    int numberOfApps = jsonApps.length();
    for (int i = 0; i < numberOfApps; i++) {
      AppInfo app = new AppInfo();
      app.setAccount(accountName);
      app.setLastUpdate(now);
      /*
       * Per app:
       * null
       * [ APP_INFO_ARRAY
       * * null
       * * packageName
       * * Nested array with details
       * * null
       * * Nested array with version details
       * * Nested array with price details
       * * Last update Date
       * * Number [1=published, 5 = draft?]
       * ]
       * null
       * [ APP_STATS_ARRAY
       * * null,
       * * Active installs
       * * Total ratings
       * * Average rating
       * * Errors
       * * Total installs
       * ]
       */
      JSONArray jsonApp = jsonApps.getJSONArray(i);
      JSONArray jsonAppInfo = jsonApp.getJSONArray(1);
      if (DEBUG) {
        pp("jsonAppInfo", jsonAppInfo);
      }
      String packageName = jsonAppInfo.getString(1);
      // Look for "tmp.7238057230750432756094760456.235728507238057230542"
      if (packageName == null
          || (packageName.startsWith("tmp.") && Character.isDigit(packageName.charAt(4)))) {
        continue;
        // Draft app
      }
      // Check number code and last updated date
      // Published: 1
      // Unpublished: 2
      // Draft: 5
      // Draft w/ in-app items?: 6
      // TODO figure out the rest and add don't just skip, filter, etc. Cf. #223
      int publishState = jsonAppInfo.getInt(7);
      Log.d(TAG, String.format("%s: publishState=%d", packageName, publishState));
      if (publishState != 1) {
        // Not a published app, skipping
        continue;
      }
      app.setPublishState(publishState);
      app.setPackageName(packageName);

      /*
       * Per app details:
       * null
       * Country code
       * App Name
       * Description
       * Unknown
       * Last what's new
       */
      if (jsonAppInfo.length() < 5) {
        // skip if we can't get all the data
        continue;
      }
      JSONArray appDetails = jsonAppInfo.getJSONArray(2).getJSONArray(1).getJSONArray(0);
      if (DEBUG) {
        pp("appDetails", appDetails);
      }
      app.setName(appDetails.getString(2));

      /*
       * Per app version details:
       * null
       * null
       * packageName
       * versionNumber
       * versionName
       * null
       * Array with app icon [null,null,null,icon]
       */
      JSONArray appVersions = jsonAppInfo.optJSONArray(4);
      if (DEBUG) {
        pp("appVersions", appVersions);
      }
      if (appVersions == null) {
        continue;
      }
      JSONArray lastAppVersionDetails =
          appVersions.getJSONArray(appVersions.length() - 1).getJSONArray(2);
      if (DEBUG) {
        pp("lastAppVersionDetails", lastAppVersionDetails);
      }
      app.setVersionName(lastAppVersionDetails.getString(4));
      app.setIconUrl(lastAppVersionDetails.getJSONArray(6).getString(3));

      // App stats
      /*
       * null,
       * Active installs
       * Total ratings
       * Average rating
       * Errors
       * Total installs
       */
      // XXX this index might not be correct for all apps?
      JSONArray jsonAppStats = jsonApp.optJSONArray(3);
      if (DEBUG) {
        pp("jsonAppStats", jsonAppStats);
      }
      if (jsonAppStats == null) {
        continue;
      }
      AppStats stats = new AppStats();
      stats.setRequestDate(now);
      if (jsonAppStats.length() < 6) {
        // no statistics (yet?) or weird format
        // TODO do we need differentiate?
        stats.setActiveInstalls(0);
        stats.setTotalDownloads(0);
        stats.setNumberOfErrors(0);
      } else {
        stats.setActiveInstalls(jsonAppStats.getInt(1));
        stats.setTotalDownloads(jsonAppStats.getInt(5));
        stats.setNumberOfErrors(jsonAppStats.optInt(4));
      }
      app.setLatestStats(stats);

      apps.add(app);
    }

    return apps;
  }