SdkStatsServicepublic class SdkStatsService extends Object Utility class to send "ping" usage reports to the server. |
Fields Summary |
---|
private static final long | PING_INTERVAL_MSECMinimum 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_TEXTUsed in the preference pane (PrefsDialog) as well. | public static final String | CHECKBOX_TEXTUsed in the preference pane (PrefsDialog) as well. | private static final String | FOOTER_TEXT | private static final String | BUTTON_TEXT | private static final String[] | LINUX_BROWSERSList 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 |
Methods Summary |
---|
private static void | actuallySendPing(java.lang.String app, java.lang.String version, long id)Unconditionally send a "ping" request to the Google toolbar server.
// 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.PreferenceStore | getPreferenceStore()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 boolean | getUserPermission()Prompt the user for whether they want to opt out of reporting.
// 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.String | normalizeVersion(java.lang.String app, java.lang.String version)Validate the supplied application version, and normalize the 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 void | openUrl(java.lang.String url)Open a URL in an external browser.
// 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 void | ping(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.
// 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();
}
|
|