// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.omaha; import android.content.Context; import android.os.Build; import android.util.Xml; import org.chromium.base.BuildInfo; import org.chromium.base.VisibleForTesting; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.StringWriter; import java.util.Locale; /** * Generates XML requests to send to the Omaha server. */ public abstract class RequestGenerator { // The Omaha specs say that new installs should use "-1". public static final int INSTALL_AGE_IMMEDIATELY_AFTER_INSTALLING = -1; private static final long MS_PER_DAY = 1000 * 60 * 60 * 24; private final Context mApplicationContext; @VisibleForTesting public RequestGenerator(Context context) { mApplicationContext = context.getApplicationContext(); } /** * Determine how long it's been since Chrome was first installed. Note that this may not * accurate for various reasons, but it shouldn't affect stats too much. */ public static long installAge( long currentTimestamp, long installTimestamp, boolean sendInstallEvent) { if (sendInstallEvent) { return INSTALL_AGE_IMMEDIATELY_AFTER_INSTALLING; } else { return Math.max(0L, (currentTimestamp - installTimestamp) / MS_PER_DAY); } } /** * Generates the XML for the current request. * Follows the format laid out at https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md * with some additional dummy values supplied. */ public String generateXML(String sessionID, String versionName, long installAge, RequestData data) throws RequestFailureException { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { serializer.setOutput(writer); serializer.startDocument("UTF-8", true); // Set up <request protocol=3.0 ...> serializer.startTag(null, "request"); serializer.attribute(null, "protocol", "3.0"); serializer.attribute(null, "version", "Android-1.0.0.0"); serializer.attribute(null, "ismachine", "1"); serializer.attribute(null, "requestid", "{" + data.getRequestID() + "}"); serializer.attribute(null, "sessionid", "{" + sessionID + "}"); serializer.attribute(null, "installsource", data.getInstallSource()); appendExtraAttributes("request", serializer); // Set up <os platform="android"... /> serializer.startTag(null, "os"); serializer.attribute(null, "platform", "android"); serializer.attribute(null, "version", Build.VERSION.RELEASE); serializer.attribute(null, "arch", "arm"); serializer.endTag(null, "os"); // Set up <app version="" ...> serializer.startTag(null, "app"); serializer.attribute(null, "brand", getBrand()); serializer.attribute(null, "client", getClient()); serializer.attribute(null, "appid", getAppId()); serializer.attribute(null, "version", versionName); serializer.attribute(null, "nextversion", ""); serializer.attribute(null, "lang", getLanguage()); serializer.attribute(null, "installage", String.valueOf(installAge)); serializer.attribute(null, "ap", getAdditionalParameters()); appendExtraAttributes("app", serializer); if (data.isSendInstallEvent()) { // Set up <event eventtype="2" eventresult="1" /> serializer.startTag(null, "event"); serializer.attribute(null, "eventtype", "2"); serializer.attribute(null, "eventresult", "1"); serializer.endTag(null, "event"); } else { // Set up <updatecheck /> serializer.startTag(null, "updatecheck"); serializer.endTag(null, "updatecheck"); // Set up <ping active="1" /> serializer.startTag(null, "ping"); serializer.attribute(null, "active", "1"); serializer.endTag(null, "ping"); } serializer.endTag(null, "app"); serializer.endTag(null, "request"); serializer.endDocument(); } catch (IOException e) { throw new RequestFailureException("Caught an IOException creating the XML: ", e); } catch (IllegalArgumentException e) { throw new RequestFailureException( "Caught an IllegalArgumentException creating the XML: ", e); } catch (IllegalStateException e) { throw new RequestFailureException( "Caught an IllegalStateException creating the XML: ", e); } return writer.toString(); } /** * Returns the application context. */ protected Context getContext() { return mApplicationContext; } /** * Returns the current Android language and region code (e.g. en-GB or de-DE). * * Note: the region code depends only on the language the user selected in Android settings. * It doesn't depend on the user's physical location. */ public String getLanguage() { Locale locale = Locale.getDefault(); if (locale.getCountry().isEmpty()) { return locale.getLanguage(); } else { return locale.getLanguage() + "-" + locale.getCountry(); } } /** * Sends additional info that might be useful for statistics generation, * including information about channel and device type. * This string is partially sanitized for dashboard viewing and because people randomly set * these strings when building their own custom Android ROMs. */ public String getAdditionalParameters() { String applicationLabel = StringSanitizer.sanitize(BuildInfo.getPackageLabel(mApplicationContext)); String brand = StringSanitizer.sanitize(Build.BRAND); String model = StringSanitizer.sanitize(Build.MODEL); return applicationLabel + ";" + brand + ";" + model; } /** * Appends extra attributes to the XML for the given tag. * @param tag Tag to add extra attributes to. * @param serializer Serializer to append the attributes to. Expects the last open tag to be * the one being appended to. */ protected void appendExtraAttributes(String tag, XmlSerializer serializer) throws IOException { } /** Returns the UUID of the Chrome version we're running. */ protected abstract String getAppId(); /** Returns the brand code. If one can't be retrieved, return "". */ protected abstract String getBrand(); /** Returns the current client ID. */ protected abstract String getClient(); /** URL for the Omaha server. */ public abstract String getServerUrl(); }