diff options
author | Makoto Onuki <omakoto@google.com> | 2012-07-18 16:13:23 -0700 |
---|---|---|
committer | Makoto Onuki <omakoto@google.com> | 2012-07-19 15:46:19 -0700 |
commit | 8a6e02add7c70666cdb506310c134af7d91c323c (patch) | |
tree | ef7bf64789d80ba98b4eb1c5988199aa99962a02 | |
parent | bcc85acf1b911a8a58c2411a103aa7d61d70d1a6 (diff) | |
download | packages_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.xml | 11 | ||||
-rw-r--r-- | res/layout/contact_dump_activity.xml | 72 | ||||
-rw-r--r-- | res/values/strings.xml | 22 | ||||
-rw-r--r-- | src/com/android/providers/contacts/debug/ContactsDumpActivity.java | 142 | ||||
-rw-r--r-- | src/com/android/providers/contacts/debug/DataExporter.java | 102 |
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)"); + } +} |