Added debug utility to copy calendar db to sd card.

Security team approved in
https://android-git.corp.google.com/g/#change,100928

Change-Id: I26560882584d4a7726f76dc13e94f3c3b559e6bd
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ecefea3..18f17fb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" />
     <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application android:label="@string/calendar_storage"
                  android:icon="@drawable/app_icon">
@@ -68,6 +69,13 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="CalendarDebugActivity" android:label="@string/calendar_info"
+                android:theme="@android:style/Theme.Holo.Dialog">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <receiver android:name="CalendarDebugReceiver">
             <intent-filter>
                  <action android:name="android.provider.Telephony.SECRET_CODE" />
diff --git a/res/layout/dialog_activity.xml b/res/layout/dialog_activity.xml
new file mode 100644
index 0000000..c6949b1
--- /dev/null
+++ b/res/layout/dialog_activity.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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_tool_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_tool_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_tool_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 b993ef4..860c3cc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,4 +40,21 @@
     <!-- What to show in messaging that refers to this provider, e.g. AccountSyncSettings -->
     <string name="provider_label">Calendar</string>
 
+    <!-- Debug tool - dialog - delete file now [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_delete_button">Delete now</string>
+
+    <!-- Debug tool - dialog - start copying [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_start_button">Start</string>
+    
+    <!-- Debug tool - Main message shown to the user when starting up the debug tool [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_message">You are about to 1) make a copy of your calendar database to the sd card/usb storage which is readable by any app and 2) email it. Remember to delete it as soon as you have successfully copied it off the device or the email is received.</string>
+
+    <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_email_sender_picker">Select a program to send your file</string>
+
+    <!-- Debug tool - email subject [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_email_subject">Calendar Db attached</string>
+
+    <!-- Debug tool - email body [CHAR LIMIT=NONE] -->
+    <string name="debug_tool_email_body">Attached is my calendar database with all my appointments and personal information. Please handle with care.</string>
 </resources>
diff --git a/src/com/android/providers/calendar/CalendarDebugActivity.java b/src/com/android/providers/calendar/CalendarDebugActivity.java
new file mode 100644
index 0000000..d06e095
--- /dev/null
+++ b/src/com/android/providers/calendar/CalendarDebugActivity.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2011 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.calendar;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+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.Window;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+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;
+
+/**
+ * Prompts the user before copying their calendar database to the SD card.
+ *
+ */
+public class CalendarDebugActivity extends Activity implements OnClickListener {
+    private static String TAG = "CalendarDebugActivity";
+    private Button mConfirmButton;
+    private Button mCancelButton;
+    private Button mDeleteButton;
+    private TextView mTextView;
+
+    private static final String OUT_FILE = "calendar.db.zip";
+    private static final String MIME_TYPE = "application/zip";
+
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_LEFT_ICON);
+
+        setContentView(R.layout.dialog_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() {
+        final boolean fileExist =
+            new File(Environment.getExternalStorageDirectory(), OUT_FILE).exists();
+        mDeleteButton.setEnabled(fileExist);
+    }
+
+    @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);
+        File outFile = new File(Environment.getExternalStorageDirectory(), OUT_FILE);
+        outFile.delete();
+    }
+
+    private class DumpDbTask extends AsyncTask<Void, Void, File> {
+
+        /**
+         * Starts spinner while task is running.
+         */
+        @Override
+        protected void onPreExecute() {
+            setProgressBarIndeterminateVisibility(true);
+        }
+
+        protected File doInBackground(Void... params) {
+            InputStream is = null;
+            ZipOutputStream os = null;
+
+            try {
+                File path = Environment.getExternalStorageDirectory();
+                File outFile = new File(path, OUT_FILE);
+                outFile.delete();
+                Log.i(TAG, "Outfile=" + outFile.getAbsolutePath());
+
+                final File inFile = getDatabasePath("calendar.db");
+                is = new FileInputStream(inFile);
+
+                os = new ZipOutputStream(new FileOutputStream(outFile));
+                os.putNextEntry(new ZipEntry(inFile.getName()));
+
+                byte[] buf = new byte[4096];
+                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, "bytes read " + totalLen);
+                os.flush();
+                os.close();
+                os = null;
+
+                // Tell the media scanner about the new file so that it is
+                // immediately available to the user.
+                MediaScannerConnection.scanFile(CalendarDebugActivity.this, new String[] {
+                    outFile.toString()
+                }, new String[] {
+                    MIME_TYPE
+                }, null);
+                return outFile;
+
+            } catch (IOException e) {
+                Log.i(TAG, "Error " + e.toString());
+            } finally {
+                try {
+                    if (is != null) {
+                        is.close();
+                    }
+                    if (os != null) {
+                        os.close();
+                    }
+                } catch (IOException e) {
+                    Log.i(TAG, "Error " + e.toString());
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Runs on the UI thread
+         */
+        @Override
+        protected void onPostExecute(File outFile) {
+            if (outFile != null) {
+                emailFile(outFile);
+            }
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        updateDeleteButton();
+        mConfirmButton.setEnabled(true);
+        mCancelButton.setEnabled(true);
+    }
+
+    private void emailFile(File file) {
+        Log.i(TAG, "Drafting email to send " + file.getAbsolutePath());
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.debug_tool_email_subject));
+        intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.debug_tool_email_body));
+        intent.setType(MIME_TYPE);
+        intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+        startActivityForResult(Intent.createChooser(intent,
+                getString(R.string.debug_tool_email_sender_picker)), 0);
+    }
+}