FileDocCategorySizeDatePackage
SdkStatsService.javaAPI DocAndroid 1.5 API17291Wed May 06 22:41:10 BST 2009com.android.sdkstats

SdkStatsService

public class SdkStatsService extends Object
Utility class to send "ping" usage reports to the server.

Fields Summary
private static final long
PING_INTERVAL_MSEC
Minimum interval between ping, in milliseconds.
private static final String
WINDOW_TITLE_TEXT
private static final String
HEADER_TEXT
private static final String
NOTICE_TEXT
public static final String
BODY_TEXT
Used in the preference pane (PrefsDialog) as well.
public static final String
CHECKBOX_TEXT
Used in the preference pane (PrefsDialog) as well.
private static final String
FOOTER_TEXT
private static final String
BUTTON_TEXT
private static final String[]
LINUX_BROWSERS
List of Linux browser commands to try, in order (see openUrl).
public static final String
PING_OPT_IN
public static final String
PING_TIME
public static final String
PING_ID
private static org.eclipse.jface.preference.PreferenceStore
sPrefStore
Constructors Summary
Methods Summary
private static voidactuallySendPing(java.lang.String app, java.lang.String version, long id)
Unconditionally send a "ping" request to the Google toolbar server.

param
app name to report in the ping
param
version to report in the ping (dotted numbers, no more than four)
param
id of the local installation
throws
IOException if the ping failed

        // Detect and report the host OS.
        String os = System.getProperty("os.name");          // $NON-NLS-1$
        if (os.startsWith("Mac OS")) {                      // $NON-NLS-1$
            os = "mac";                                     // $NON-NLS-1$
        } else if (os.startsWith("Windows")) {              // $NON-NLS-1$
            os = "win";                                     // $NON-NLS-1$
        } else if (os.startsWith("Linux")) {                // $NON-NLS-1$
            os = "linux";                                   // $NON-NLS-1$
        } else {
            // Unknown -- surprising -- send it verbatim so we can see it.
            os = URLEncoder.encode(os);
        }

        // Include the application's name as part of the as= value.
        // Share the user ID for all apps, to allow unified activity reports.

        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=" + os);                               // $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$
        }
    
public static synchronized org.eclipse.jface.preference.PreferenceStoregetPreferenceStore()
Returns the DDMS {@link PreferenceStore}.

        if (sPrefStore == null) {
            // get the location of the preferences
            String homeDir = null;
            try {
                homeDir = AndroidLocation.getFolder();
            } catch (AndroidLocationException e1) {
                // pass, we'll do a dummy store since homeDir is null
            }

            if (homeDir != null) {
                String rcFileName = homeDir + "ddms.cfg"; //$NON-NLS-1$
                
                // also look for an old pref file in the previous location
                String oldPrefPath = System.getProperty("user.home") //$NON-NLS-1$
                    + File.separator + ".ddmsrc"; //$NON-NLS-1$
                File oldPrefFile = new File(oldPrefPath);
                if (oldPrefFile.isFile()) {
                    try {
                        PreferenceStore oldStore = new PreferenceStore(oldPrefPath);
                        oldStore.load();
                        
                        oldStore.save(new FileOutputStream(rcFileName), "");
                        oldPrefFile.delete();
                        
                        PreferenceStore newStore = new PreferenceStore(rcFileName);
                        newStore.load();
                        sPrefStore = newStore;
                    } catch (IOException e) {
                        // create a new empty store.
                        sPrefStore = new PreferenceStore(rcFileName);
                    }
                } else {
                    sPrefStore = new PreferenceStore(rcFileName);

                    try {
                        sPrefStore.load();
                    } catch (IOException e) {
                        System.err.println("Error Loading Preferences");
                    }
                }
            } else {
                sPrefStore = new PreferenceStore();
            }
        }
        
        return sPrefStore;
    
private static booleangetUserPermission()
Prompt the user for whether they want to opt out of reporting.

return
whether the user allows reporting (they do not opt out).

        // Use dialog trim for the shell, but without a close button.
        final Display display = new Display();
        final Shell shell = new Shell(display, SWT.TITLE | SWT.BORDER);
        shell.setText(WINDOW_TITLE_TEXT);
        shell.setLayout(new GridLayout(1, false));  // 1 column

        // Take the default font and scale it up for the title.
        final Label title = new Label(shell, SWT.CENTER | SWT.WRAP);
        final FontData[] fontdata = title.getFont().getFontData();
        for (int i = 0; i < fontdata.length; i++) {
            fontdata[i].setHeight(fontdata[i].getHeight() * 4 / 3);
        }
        title.setFont(new Font(display, fontdata));
        title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        title.setText(HEADER_TEXT);

        final Label notice = new Label(shell, SWT.WRAP);
        notice.setFont(title.getFont());
        notice.setForeground(new Color(display, 255, 0, 0));
        notice.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        notice.setText(NOTICE_TEXT);

        final Link text = new Link(shell, SWT.WRAP);
        text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        text.setText(BODY_TEXT);
        text.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent event) {
                openUrl(event.text);
            }
        });

        final Button checkbox = new Button(shell, SWT.CHECK);
        checkbox.setSelection(true);  // Opt-in by default.
        checkbox.setText(CHECKBOX_TEXT);

        final Link footer = new Link(shell, SWT.WRAP);
        footer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        footer.setText(FOOTER_TEXT);

        // Whether the user gave permission (size-1 array for writing to).
        // Initialize to false, set when the user clicks the button.
        final boolean[] permission = new boolean[] { false };

        final Button button = new Button(shell, SWT.PUSH);
        button.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
        button.setText(BUTTON_TEXT);
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent event) {
                permission[0] = checkbox.getSelection();
                shell.close();
            }
        });

        // Size the window to a fixed width, as high as necessary, centered.
        final Point size = shell.computeSize(450, SWT.DEFAULT, true);
        final Rectangle screen = display.getClientArea();
        shell.setBounds(
            screen.x + screen.width / 2 - size.x / 2,
            screen.y + screen.height / 2 - size.y / 2,
            size.x, size.y);

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }

        display.dispose();  // Otherwise ddms' own Display can't be created
        return permission[0];
    
private static java.lang.StringnormalizeVersion(java.lang.String app, java.lang.String version)
Validate the supplied application version, and normalize the version.

param
app to report
param
version supplied by caller
return
normalized dotted quad version

        // Application name must contain only word characters (no punctuaation)
        if (!app.matches("\\w+")) {
            throw new IllegalArgumentException("Bad app name: " + app);
        }

        // Version must be between 1 and 4 dotted numbers
        String[] numbers = version.split("\\.");
        if (numbers.length > 4) {
            throw new IllegalArgumentException("Bad version: " + version);
        }
        for (String part: numbers) {
            if (!part.matches("\\d+")) {
                throw new IllegalArgumentException("Bad version: " + version);
            }
        }

        // 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");
        }
        return normal.toString();
    
public static voidopenUrl(java.lang.String url)
Open a URL in an external browser.

param
url to open - MUST be sanitized and properly formed!

        // TODO: consider using something like BrowserLauncher2
        // (http://browserlaunch2.sourceforge.net/) instead of these hacks.

        // SWT's Program.launch() should work on Mac, Windows, and GNOME
        // (because the OS shell knows how to launch a default browser).
        if (!Program.launch(url)) {
            // Must be Linux non-GNOME (or something else broke).
            // Try a few Linux browser commands in the background.
            new Thread() {
                @Override
                public void run() {
                    for (String cmd : LINUX_BROWSERS) {
                        cmd = cmd.replaceAll("%URL%", url);  // $NON-NLS-1$
                        try {
                            Process proc = Runtime.getRuntime().exec(cmd);
                            if (proc.waitFor() == 0) break;  // Success!
                        } catch (InterruptedException e) {
                            // Should never happen!
                            throw new RuntimeException(e);
                        } catch (IOException e) {
                            // Swallow the exception and try the next browser.
                        }
                    }

                    // TODO: Pop up some sort of error here?
                    // (We're in a new thread; can't use the existing Display.)
                }
            }.start();
        }
    
public static voidping(java.lang.String app, java.lang.String version)
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. If this is the first time, notify the user and offer an opt-out. Note: UI operations (if any) are synchronous, but the actual ping (if any) is sent in a non-daemon background thread.

param
app name to report in the ping
param
version to report in the ping

    
                                                                                  
             
        // Validate the application and version input.
        final String normalVersion = normalizeVersion(app, version);

        // Unique, randomly assigned ID for this installation.
        PreferenceStore prefs = getPreferenceStore();
        if (prefs != null) {
            if (!prefs.contains(PING_ID)) {
                // First time: make up a new ID.  TODO: Use something more random?
                prefs.setValue(PING_ID, new Random().nextLong());
    
                // Also give them a chance to opt out.
                prefs.setValue(PING_OPT_IN, getUserPermission());
                try {
                    prefs.save();
                }
                catch (IOException ioe) {
                }
            }
    
            // If the user has not opted in, do nothing and quietly return.
            if (!prefs.getBoolean(PING_OPT_IN)) {
                // user opted out.
                return;
            }
    
            // If the last ping *for this app* was too recent, do nothing.
            String timePref = PING_TIME + "." + app;  // $NON-NLS-1$
            long now = System.currentTimeMillis();
            long then = prefs.getLong(timePref);
            if (now - then < PING_INTERVAL_MSEC) {
                // too soon after a ping.
                return;
            }
    
            // Record the time of the attempt, whether or not it succeeds.
            prefs.setValue(timePref, now);
            try {
                prefs.save();
            }
            catch (IOException ioe) {
            }
    
            // Send the ping itself in the background (don't block if the
            // network is down or slow or confused).
            final long id = prefs.getLong(PING_ID);
            new Thread() {
                @Override
                public void run() {
                    try {
                        actuallySendPing(app, normalVersion, id);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }