aboutsummaryrefslogtreecommitdiffstats
path: root/sdkstats/src/com/android/sdkstats/SdkStatsService.java
diff options
context:
space:
mode:
Diffstat (limited to 'sdkstats/src/com/android/sdkstats/SdkStatsService.java')
-rw-r--r--sdkstats/src/com/android/sdkstats/SdkStatsService.java544
1 files changed, 0 insertions, 544 deletions
diff --git a/sdkstats/src/com/android/sdkstats/SdkStatsService.java b/sdkstats/src/com/android/sdkstats/SdkStatsService.java
deleted file mode 100644
index c490fed..0000000
--- a/sdkstats/src/com/android/sdkstats/SdkStatsService.java
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * 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.android.sdkstats;
-
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Utility class to send "ping" usage reports to the server. */
-public class SdkStatsService {
-
- protected static final String SYS_PROP_OS_ARCH = "os.arch"; //$NON-NLS-1$
- protected static final String SYS_PROP_JAVA_VERSION = "java.version"; //$NON-NLS-1$
- protected static final String SYS_PROP_OS_VERSION = "os.version"; //$NON-NLS-1$
- protected static final String SYS_PROP_OS_NAME = "os.name"; //$NON-NLS-1$
-
- /** Minimum interval between ping, in milliseconds. */
- private static final long PING_INTERVAL_MSEC = 86400 * 1000; // 1 day
-
- private static final boolean DEBUG = System.getenv("ANDROID_DEBUG_PING") != null; //$NON-NLS-1$
-
- private DdmsPreferenceStore mStore = new DdmsPreferenceStore();
-
- public SdkStatsService() {
- }
-
- /**
- * Send a "ping" to the Google toolbar server, if enough time has
- * elapsed since the last ping, and if the user has not opted out.
- * <p/>
- * This is a simplified version of {@link #ping(String[])} that only
- * sends an "application" name and a "version" string. See the explanation
- * there for details.
- *
- * @param app The application name that reports the ping (e.g. "emulator" or "ddms".)
- * Valid characters are a-zA-Z0-9 only.
- * @param version The version string (e.g. "12" or "1.2.3.4", 4 groups max.)
- * @see #ping(String[])
- */
- public void ping(String app, String version) {
- doPing(app, version, null);
- }
-
- /**
- * Send a "ping" to the Google toolbar server, if enough time has
- * elapsed since the last ping, and if the user has not opted out.
- * <p/>
- * The ping will not be sent if the user opt out dialog has not been shown yet.
- * Use {@link #checkUserPermissionForPing(Shell)} to display the dialog requesting
- * user permissions.
- * <p/>
- * Note: The actual ping (if any) is sent in a <i>non-daemon</i> background thread.
- * <p/>
- * The arguments are defined as follow:
- * <ul>
- * <li>Argument 0 is the "ping" command and is ignored.</li>
- * <li>Argument 1 is the application name that reports the ping (e.g. "emulator" or "ddms".)
- * Valid characters are a-zA-Z0-9 only.</li>
- * <li>Argument 2 is the version string (e.g. "12" or "1.2.3.4", 4 groups max.)</li>
- * <li>Arguments 3+ are optional and depend on the application name.</li>
- * <li>"emulator" application currently has 3 optional arguments:
- * <ul>
- * <li>Arugment 3: android_gl_vendor</li>
- * <li>Arugment 4: android_gl_renderer</li>
- * <li>Arugment 5: android_gl_version</li>
- * </ul>
- * </li>
- * </ul>
- *
- * @param arguments A non-empty non-null array of arguments to the ping as described above.
- */
- public void ping(String[] arguments) {
- if (arguments == null || arguments.length < 3) {
- throw new IllegalArgumentException(
- "Invalid ping arguments: expected ['ping', app, version] but got " +
- (arguments == null ? "null" : Arrays.toString(arguments)));
- }
- int len = arguments.length;
- String app = arguments[1];
- String version = arguments[2];
-
- Map<String, String> extras = new HashMap<String, String>();
-
- if ("emulator".equals(app)) { //$NON-NLS-1$
- if (len > 3) {
- extras.put("glm", sanitizeGlArg(arguments[3])); //$NON-NLS-1$ vendor
- }
- if (len > 4) {
- extras.put("glr", sanitizeGlArg(arguments[4])); //$NON-NLS-1$ renderer
- }
- if (len > 5) {
- extras.put("glv", sanitizeGlArg(arguments[5])); //$NON-NLS-1$ version
- }
- }
-
- doPing(app, version, extras);
- }
-
- private String sanitizeGlArg(String arg) {
- if (arg == null) {
- arg = ""; //$NON-NLS-1$
- } else {
- try {
- arg = arg.trim();
- arg = arg.replaceAll("[^A-Za-z0-9\\s_()./-]", " "); //$NON-NLS-1$ //$NON-NLS-2$
- arg = arg.replaceAll("\\s\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
-
- // Guard from arbitrarily long parameters
- if (arg.length() > 128) {
- arg = arg.substring(0, 128);
- }
-
- arg = URLEncoder.encode(arg, "UTF-8"); //$NON-NLS-1$
- } catch (UnsupportedEncodingException e) {
- arg = ""; //$NON-NLS-1$
- }
- }
-
- return arg;
- }
-
- /**
- * Display a dialog to the user providing information about the ping service,
- * and whether they'd like to opt-out of it.
- *
- * Once the dialog has been shown, it sets a preference internally indicating
- * that the user has viewed this dialog.
- */
- public void checkUserPermissionForPing(Shell parent) {
- if (!mStore.hasPingId()) {
- askUserPermissionForPing(parent);
- mStore.generateNewPingId();
- }
- }
-
- /**
- * Prompt the user for whether they want to opt out of reporting, and save the user
- * input in preferences.
- */
- private void askUserPermissionForPing(final Shell parent) {
- final Display display = parent.getDisplay();
- display.syncExec(new Runnable() {
- @Override
- public void run() {
- SdkStatsPermissionDialog dialog = new SdkStatsPermissionDialog(parent);
- dialog.open();
- mStore.setPingOptIn(dialog.getPingUserPreference());
- }
- });
- }
-
- // -------
-
- /**
- * Pings the usage stats server, as long as the prefs contain the opt-in boolean
- *
- * @param app The application name that reports the ping (e.g. "emulator" or "ddms".)
- * Will be normalized. Valid characters are a-zA-Z0-9 only.
- * @param version The version string (e.g. "12" or "1.2.3.4", 4 groups max.)
- * @param extras Extra key/value parameters to send. They are send as-is and must
- * already be well suited and escaped using {@link URLEncoder#encode(String, String)}.
- */
- protected void doPing(String app, String version, final Map<String, String> extras) {
- // Note: if you change the implementation here, you also need to change
- // the overloaded SdkStatsServiceTest.doPing() used for testing.
-
- // Validate the application and version input.
- final String nApp = normalizeAppName(app);
- final String nVersion = normalizeVersion(version);
-
- // If the user has not opted in, do nothing and quietly return.
- if (!mStore.isPingOptIn()) {
- // user opted out.
- return;
- }
-
- // If the last ping *for this app* was too recent, do nothing.
- long now = System.currentTimeMillis();
- long then = mStore.getPingTime(app);
- if (now - then < PING_INTERVAL_MSEC) {
- // too soon after a ping.
- return;
- }
-
- // Record the time of the attempt, whether or not it succeeds.
- mStore.setPingTime(app, now);
-
- // Send the ping itself in the background (don't block if the
- // network is down or slow or confused).
- final long id = mStore.getPingId();
- new Thread() {
- @Override
- public void run() {
- try {
- URL url = createPingUrl(nApp, nVersion, id, extras);
- actuallySendPing(url);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }
-
-
- /**
- * Unconditionally send a "ping" request to the server.
- *
- * @param url The URL to send to the server.
- * * @throws IOException if the ping failed
- */
- private void actuallySendPing(URL url) throws IOException {
- assert url != null;
-
- if (DEBUG) {
- System.err.println("Ping: " + url.toString()); //$NON-NLS-1$
- }
-
- // Discard the actual response, but make sure it reads OK
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-
- // Believe it or not, a 404 response indicates success:
- // the ping was logged, but no update is configured.
- if (conn.getResponseCode() != HttpURLConnection.HTTP_OK &&
- conn.getResponseCode() != HttpURLConnection.HTTP_NOT_FOUND) {
- throw new IOException(
- conn.getResponseMessage() + ": " + url); //$NON-NLS-1$
- }
- }
-
- /**
- * Compute the ping URL to send the data to the server.
- *
- * @param app The application name that reports the ping (e.g. "emulator" or "ddms".)
- * Valid characters are a-zA-Z0-9 only.
- * @param version The version string already formatted as a 4 dotted group (e.g. "1.2.3.4".)
- * @param id of the local installation
- * @param extras Extra key/value parameters to send. They are send as-is and must
- * already be well suited and escaped using {@link URLEncoder#encode(String, String)}.
- */
- protected URL createPingUrl(String app, String version, long id, Map<String, String> extras)
- throws UnsupportedEncodingException, MalformedURLException {
-
- String osName = URLEncoder.encode(getOsName(), "UTF-8"); //$NON-NLS-1$
- String osArch = URLEncoder.encode(getOsArch(), "UTF-8"); //$NON-NLS-1$
- String jvmArch = URLEncoder.encode(getJvmInfo(), "UTF-8"); //$NON-NLS-1$
-
- // Include the application's name as part of the as= value.
- // Share the user ID for all apps, to allow unified activity reports.
-
- String extraStr = ""; //$NON-NLS-1$
- if (extras != null && !extras.isEmpty()) {
- StringBuilder sb = new StringBuilder();
- for (Map.Entry<String, String> entry : extras.entrySet()) {
- sb.append('&').append(entry.getKey()).append('=').append(entry.getValue());
- }
- extraStr = sb.toString();
- }
-
- URL url = new URL(
- "http", //$NON-NLS-1$
- "tools.google.com", //$NON-NLS-1$
- "/service/update?as=androidsdk_" + app + //$NON-NLS-1$
- "&id=" + Long.toHexString(id) + //$NON-NLS-1$
- "&version=" + version + //$NON-NLS-1$
- "&os=" + osName + //$NON-NLS-1$
- "&osa=" + osArch + //$NON-NLS-1$
- "&vma=" + jvmArch + //$NON-NLS-1$
- extraStr);
- return url;
- }
-
- /**
- * Detects and reports the host OS: "linux", "win" or "mac".
- * For Windows and Mac also append the version, so for example
- * Win XP will return win-5.1.
- */
- protected String getOsName() { // made protected for testing
- String os = getSystemProperty(SYS_PROP_OS_NAME);
-
- if (os == null || os.length() == 0) {
- return "unknown"; //$NON-NLS-1$
- }
-
- String os2 = os.toLowerCase(Locale.US);
-
- if (os2.startsWith("mac")) { //$NON-NLS-1$
- os = "mac"; //$NON-NLS-1$
- String osVers = getOsVersion();
- if (osVers != null) {
- os = os + '-' + osVers;
- }
- } else if (os2.startsWith("win")) { //$NON-NLS-1$
- os = "win"; //$NON-NLS-1$
- String osVers = getOsVersion();
- if (osVers != null) {
- os = os + '-' + osVers;
- }
- } else if (os2.startsWith("linux")) { //$NON-NLS-1$
- os = "linux"; //$NON-NLS-1$
-
- } else if (os.length() > 32) {
- // Unknown -- send it verbatim so we can see it
- // but protect against arbitrarily long values
- os = os.substring(0, 32);
- }
- return os;
- }
-
- /**
- * Detects and returns the OS architecture: x86, x86_64, ppc.
- * This may differ or be equal to the JVM architecture in the sense that
- * a 64-bit OS can run a 32-bit JVM.
- */
- protected String getOsArch() { // made protected for testing
- String arch = getJvmArch();
-
- if ("x86_64".equals(arch)) { //$NON-NLS-1$
- // This is a simple case: the JVM runs in 64-bit so the
- // OS must be a 64-bit one.
- return arch;
-
- } else if ("x86".equals(arch)) { //$NON-NLS-1$
- // This is the misleading case: the JVM is 32-bit but the OS
- // might be either 32 or 64. We can't tell just from this
- // property.
- // Macs are always on 64-bit, so we just need to figure it
- // out for Windows and Linux.
-
- String os = getOsName();
- if (os.startsWith("win")) { //$NON-NLS-1$
- // When WOW64 emulates a 32-bit environment under a 64-bit OS,
- // it sets PROCESSOR_ARCHITEW6432 to AMD64 or IA64 accordingly.
- // Ref: http://msdn.microsoft.com/en-us/library/aa384274(v=vs.85).aspx
-
- String w6432 = getSystemEnv("PROCESSOR_ARCHITEW6432"); //$NON-NLS-1$
- if (w6432 != null && w6432.indexOf("64") != -1) { //$NON-NLS-1$
- return "x86_64"; //$NON-NLS-1$
- }
- } else if (os.startsWith("linux")) { //$NON-NLS-1$
- // Let's try the obvious. This works in Ubuntu and Debian
- String s = getSystemEnv("HOSTTYPE"); //$NON-NLS-1$
-
- s = sanitizeOsArch(s);
- if (s.indexOf("86") != -1) { //$NON-NLS-1$
- arch = s;
- }
- }
- }
-
- return arch;
- }
-
- /**
- * Returns the version of the OS version if it is defined as X.Y, or null otherwise.
- * <p/>
- * Example of returned versions can be found at http://lopica.sourceforge.net/os.html
- * <p/>
- * This method removes any exiting micro versions.
- * Returns null if the version doesn't match X.Y.Z.
- */
- protected String getOsVersion() { // made protected for testing
- Pattern p = Pattern.compile("(\\d+)\\.(\\d+).*"); //$NON-NLS-1$
- String osVers = getSystemProperty(SYS_PROP_OS_VERSION);
- if (osVers != null && osVers.length() > 0) {
- Matcher m = p.matcher(osVers);
- if (m.matches()) {
- return m.group(1) + '.' + m.group(2);
- }
- }
- return null;
- }
-
- /**
- * Detects and returns the JVM info: version + architecture.
- * Examples: 1.4-ppc, 1.6-x86, 1.7-x86_64
- */
- protected String getJvmInfo() { // made protected for testing
- return getJvmVersion() + '-' + getJvmArch();
- }
-
- /**
- * Returns the major.minor Java version.
- * <p/>
- * The "java.version" property returns something like "1.6.0_20"
- * of which we want to return "1.6".
- */
- protected String getJvmVersion() { // made protected for testing
- String version = getSystemProperty(SYS_PROP_JAVA_VERSION);
-
- if (version == null || version.length() == 0) {
- return "unknown"; //$NON-NLS-1$
- }
-
- Pattern p = Pattern.compile("(\\d+)\\.(\\d+).*"); //$NON-NLS-1$
- Matcher m = p.matcher(version);
- if (m.matches()) {
- return m.group(1) + '.' + m.group(2);
- }
-
- // Unknown version. Send it as-is within a reasonable size limit.
- if (version.length() > 8) {
- version = version.substring(0, 8);
- }
- return version;
- }
-
- /**
- * Detects and returns the JVM architecture.
- * <p/>
- * The HotSpot JVM has a private property for this, "sun.arch.data.model",
- * which returns either "32" or "64". However it's not in any kind of spec.
- * <p/>
- * What we want is to know whether the JVM is running in 32-bit or 64-bit and
- * the best indicator is to use the "os.arch" property.
- * - On a 32-bit system, only a 32-bit JVM can run so it will be x86 or ppc.<br/>
- * - On a 64-bit system, a 32-bit JVM will also return x86 since the OS needs
- * to masquerade as a 32-bit OS for backward compatibility.<br/>
- * - On a 64-bit system, a 64-bit JVM will properly return x86_64.
- * <pre>
- * JVM: Java 32-bit Java 64-bit
- * Windows: x86 x86_64
- * Linux: x86 x86_64
- * Mac untested x86_64
- * </pre>
- */
- protected String getJvmArch() { // made protected for testing
- String arch = getSystemProperty(SYS_PROP_OS_ARCH);
- return sanitizeOsArch(arch);
- }
-
- private String sanitizeOsArch(String arch) {
- if (arch == null || arch.length() == 0) {
- return "unknown"; //$NON-NLS-1$
- }
-
- if (arch.equalsIgnoreCase("x86_64") || //$NON-NLS-1$
- arch.equalsIgnoreCase("ia64") || //$NON-NLS-1$
- arch.equalsIgnoreCase("amd64")) { //$NON-NLS-1$
- return "x86_64"; //$NON-NLS-1$
- }
-
- if (arch.length() >= 4 && arch.charAt(0) == 'i' && arch.indexOf("86") == 2) { //$NON-NLS-1$
- // Any variation of iX86 counts as x86 (i386, i486, i686).
- return "x86"; //$NON-NLS-1$
- }
-
- if (arch.equalsIgnoreCase("PowerPC")) { //$NON-NLS-1$
- return "ppc"; //$NON-NLS-1$
- }
-
- // Unknown arch. Send it as-is but protect against arbitrarily long values.
- if (arch.length() > 32) {
- arch = arch.substring(0, 32);
- }
- return arch;
- }
-
- /**
- * Normalize the supplied application name.
- *
- * @param app to report
- */
- protected String normalizeAppName(String app) {
- // Filter out \W , non-word character: [^a-zA-Z_0-9]
- String app2 = app.replaceAll("\\W", ""); //$NON-NLS-1$ //$NON-NLS-2$
-
- if (app.length() == 0) {
- throw new IllegalArgumentException("Bad app name: " + app); //$NON-NLS-1$
- }
-
- return app2;
- }
-
- /**
- * Validate the supplied application version, and normalize the version.
- *
- * @param version supplied by caller
- * @return normalized dotted quad version
- */
- protected String normalizeVersion(String version) {
-
- // Version must be between 1 and 4 dotted numbers
- String[] numbers = version.split("\\."); //$NON-NLS-1$
- if (numbers.length > 4) {
- throw new IllegalArgumentException("Bad version: " + version); //$NON-NLS-1$
- }
- for (String part: numbers) {
- if (!part.matches("\\d+")) { //$NON-NLS-1$
- throw new IllegalArgumentException("Bad version: " + version); //$NON-NLS-1$
- }
- }
-
- // Always output 4 numbers, even if fewer were supplied (pad with .0)
- StringBuffer normal = new StringBuffer(numbers[0]);
- for (int i = 1; i < 4; i++) {
- normal.append('.').append(i < numbers.length ? numbers[i] : "0"); //$NON-NLS-1$
- }
- return normal.toString();
- }
-
- /**
- * Calls {@link System#getProperty(String)}.
- * Allows unit-test to override the return value.
- * @see System#getProperty(String)
- */
- protected String getSystemProperty(String name) {
- return System.getProperty(name);
- }
-
- /**
- * Calls {@link System#getenv(String)}.
- * Allows unit-test to override the return value.
- * @see System#getenv(String)
- */
- protected String getSystemEnv(String name) {
- return System.getenv(name);
- }
-}