summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMakoto Onuki <omakoto@google.com>2012-07-18 16:13:23 -0700
committerMakoto Onuki <omakoto@google.com>2012-07-19 15:46:19 -0700
commit8a6e02add7c70666cdb506310c134af7d91c323c (patch)
treeef7bf64789d80ba98b4eb1c5988199aa99962a02
parentbcc85acf1b911a8a58c2411a103aa7d61d70d1a6 (diff)
downloadpackages_providers_ContactsProvider-8a6e02add7c70666cdb506310c134af7d91c323c.zip
packages_providers_ContactsProvider-8a6e02add7c70666cdb506310c134af7d91c323c.tar.gz
packages_providers_ContactsProvider-8a6e02add7c70666cdb506310c134af7d91c323c.tar.bz2
Add debug activity to export all data files as a zip
This will allow us to collect contacts database files even from user build devices where "adb root" is disabled. This is simialr to what CalendarProvider does in CalendarDebugActivity. The difference is it'll export all files under "/data/data/com.android.providers.contacts/", including the profile db and highres photo files. To launch the activity: adb shell am start -a com.android.providers.contacts.DUMP_DATABASE I'm planning to add somethig to the people app to fire off this intent, so that users will be able to do it without adb. The activity will show a warning message with "Start", "Delete" and "Cancel" buttons. - "Cancel" will close the dialog. - "Start" will create a zip file. After that, it'll present the activity chooser to let the user choose which app to use to send it with. - "Delete" will delete the ZIP file. We need to make sure to ask the user to do this once sending email is succeessfully finished (unfortunately there's no way for us to detect it programmatically), as any apps with the "read sdcard" parmission will be able to read it otherwise. In the future, we may want to add options to, for example, exclude the profile db or exclude highres pictures. Bug 6813842 Change-Id: Id181efad65194ed39b0a0bc1226252da62b8927e
-rw-r--r--AndroidManifest.xml11
-rw-r--r--res/layout/contact_dump_activity.xml72
-rw-r--r--res/values/strings.xml22
-rw-r--r--src/com/android/providers/contacts/debug/ContactsDumpActivity.java142
-rw-r--r--src/com/android/providers/contacts/debug/DataExporter.java102
5 files changed, 349 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6f74368..f2aeac9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -96,5 +96,16 @@
</receiver>
<service android:name="VoicemailCleanupService"/>
+
+ <activity android:name=".debug.ContactsDumpActivity"
+ android:label="@string/debug_dump_title"
+ android:theme="@android:style/Theme.Holo.Dialog"
+ >
+ <intent-filter>
+ <action android:name="com.android.providers.contacts.DUMP_DATABASE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/res/layout/contact_dump_activity.xml b/res/layout/contact_dump_activity.xml
new file mode 100644
index 0000000..557b5bd
--- /dev/null
+++ b/res/layout/contact_dump_activity.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="4dp"
+ android:gravity="center_horizontal">
+
+ <!-- Message to show to use. -->
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:gravity="center_vertical|left"
+ android:layout_weight="1">
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:text="@string/debug_dump_database_message"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </ScrollView>
+
+ <!-- Alert dialog style buttons along the bottom. -->
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:measureWithLargestChild="true">
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:id="@+id/confirm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:onClick="onClick"
+ android:text="@string/debug_dump_start_button" />
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:id="@+id/delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:onClick="onClick"
+ android:text="@string/debug_dump_delete_button"
+ android:enabled="false" />
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:onClick="onClick"
+ android:text="@android:string/no" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aaa7f44..e17b5ed 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -58,4 +58,26 @@
Note that the trailing space is important, and that to achieve it we have to wrap the
string in double quotes. -->
<string name="voicemail_from_column">"Voicemail from "</string>
+
+ <!-- Debug tool - title of the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_title">Copy contacts database</string>
+
+ <!-- Debug tool - message shown to the user on the dialog which copies the contact database into the external storage. [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_database_message">You are about to 1) make a copy of your database which includes all contacts related information and all call log to the SD card/USB storage, which is readable by any app, and 2) email it. Remember to delete the copy as soon as you have successfully copied it off the device or the email is received.</string>
+
+ <!-- Debug tool - dialog button- delete file now [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_delete_button">Delete now</string>
+
+ <!-- Debug tool - dialog button - start copying [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_start_button">Start</string>
+
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_sender_picker">Choose a program to send your file</string>
+
+ <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_subject">Contacts Db attached</string>
+
+ <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+ <string name="debug_dump_email_body">Attached is my contacts database with all my contacts information. Handle with care.</string>
+
</resources>
diff --git a/src/com/android/providers/contacts/debug/ContactsDumpActivity.java b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
new file mode 100644
index 0000000..530e779
--- /dev/null
+++ b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 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.providers.contacts.debug;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.Button;
+
+import com.android.providers.contacts.R;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Activity to export all app data files as a zip file on sdcard, and send it via email.
+ *
+ * Usage:
+ * adb shell am start -a com.android.providers.contacts.DUMP_DATABASE
+ */
+public class ContactsDumpActivity extends Activity implements OnClickListener {
+ private static String TAG = "ContactsDumpActivity";
+ private Button mConfirmButton;
+ private Button mCancelButton;
+ private Button mDeleteButton;
+
+ private static final File OUT_FILE = new File(Environment.getExternalStorageDirectory(),
+ "contacts.db.zip");
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Be sure to call the super class.
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_LEFT_ICON);
+
+ setContentView(R.layout.contact_dump_activity);
+
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+ android.R.drawable.ic_dialog_alert);
+
+ mConfirmButton = (Button) findViewById(R.id.confirm);
+ mCancelButton = (Button) findViewById(R.id.cancel);
+ mDeleteButton = (Button) findViewById(R.id.delete);
+ updateDeleteButton();
+ }
+
+ private void updateDeleteButton() {
+ mDeleteButton.setEnabled(OUT_FILE.exists());
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.confirm:
+ mConfirmButton.setEnabled(false);
+ mCancelButton.setEnabled(false);
+ new DumpDbTask().execute();
+ break;
+ case R.id.delete:
+ cleanup();
+ updateDeleteButton();
+ break;
+ case R.id.cancel:
+ finish();
+ break;
+ }
+ }
+
+ private void cleanup() {
+ Log.i(TAG, "Deleting " + OUT_FILE);
+ OUT_FILE.delete();
+ }
+
+ private class DumpDbTask extends AsyncTask<Void, Void, Boolean> {
+ /**
+ * Starts spinner while task is running.
+ */
+ @Override
+ protected void onPreExecute() {
+ setProgressBarIndeterminateVisibility(true);
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ try {
+ DataExporter.exportData(getApplicationContext(), OUT_FILE);
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to export", e);
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean success) {
+ if (success != null && success) {
+ emailFile(OUT_FILE);
+ }
+ }
+ }
+
+ private void emailFile(File file) {
+ Log.i(TAG, "Drafting email to send " + file.getAbsolutePath() +
+ " (" + file.length() + " bytes)");
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.debug_dump_email_subject));
+ intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.debug_dump_email_body));
+ intent.setType(DataExporter.ZIP_MIME_TYPE);
+ intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+ startActivityForResult(Intent.createChooser(intent,
+ getString(R.string.debug_dump_email_sender_picker)), 0);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ updateDeleteButton();
+ mConfirmButton.setEnabled(true);
+ mCancelButton.setEnabled(true);
+ }
+}
diff --git a/src/com/android/providers/contacts/debug/DataExporter.java b/src/com/android/providers/contacts/debug/DataExporter.java
new file mode 100644
index 0000000..886314b
--- /dev/null
+++ b/src/com/android/providers/contacts/debug/DataExporter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012 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.providers.contacts.debug;
+
+import android.content.Context;
+import android.media.MediaScannerConnection;
+import android.util.Log;
+
+import com.google.common.io.Closeables;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Compress all files under the app data dir into a single zip file.
+ */
+public class DataExporter {
+ private static String TAG = "DataExporter";
+
+ public static final String ZIP_MIME_TYPE = "application/zip";
+
+ /**
+ * Compress all files under the app data dir into a single zip file.
+ */
+ public static void exportData(Context context, File outFile) throws IOException {
+ outFile.delete();
+ Log.i(TAG, "Outfile=" + outFile.getAbsolutePath());
+
+ final ZipOutputStream os = new ZipOutputStream(new FileOutputStream(outFile));
+ try {
+ addDirectory(os, context.getFilesDir().getParentFile(), "contacts-files");
+ } finally {
+ Closeables.closeQuietly(os);
+ }
+ // Tell the media scanner about the new file so that it is
+ // immediately available to the user.
+ MediaScannerConnection.scanFile(context,
+ new String[] {outFile.toString()},
+ new String[] {ZIP_MIME_TYPE}, null);
+ }
+
+ /**
+ * Add all files under {@code current} to {@code os} zip stream
+ */
+ private static void addDirectory(ZipOutputStream os, File current, String storedPath)
+ throws IOException {
+ for (File child : current.listFiles()) {
+ final String childStoredPath = storedPath + "/" + child.getName();
+
+ if (child.isDirectory()) {
+ addDirectory(os, child, childStoredPath);
+ } else if (child.isFile()) {
+ addFile(os, child, childStoredPath);
+ } else {
+ // Shouldn't happen; skip.
+ }
+ }
+ }
+
+ /**
+ * Add a single file {@code current} to {@code os} zip stream using the file name
+ * {@code storedPath}.
+ */
+ private static void addFile(ZipOutputStream os, File current, String storedPath)
+ throws IOException {
+ final InputStream is = new FileInputStream(current);
+ os.putNextEntry(new ZipEntry(storedPath));
+
+ final byte[] buf = new byte[32 * 1024];
+ int totalLen = 0;
+ while (true) {
+ int len = is.read(buf);
+ if (len <= 0) {
+ break;
+ }
+ os.write(buf, 0, len);
+ totalLen += len;
+ }
+ os.closeEntry();
+ Log.i(TAG, "Added " + current.getAbsolutePath() + " as " + storedPath +
+ " (" + totalLen + " bytes)");
+ }
+}