Refactored the BUGREPORT_RECEIVED logic into a new service.

Although this Change doesn't modify the workflow logic itself, it paves
the way for upcoming changes where the overall bug report progress will
be monitored by this service.

BUG: 25794470
Change-Id: Id0c867b04b595b5d31d11531405408f3423dd68b
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4f087db..bf3982d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -147,5 +147,9 @@
                 <action android:name="android.intent.action.BUGREPORT_FINISHED" />
             </intent-filter>
         </receiver>
+
+        <service
+            android:name=".BugreportProgressService"
+            android:exported="false"/>
     </application>
 </manifest>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
new file mode 100644
index 0000000..a2030ef
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2015 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.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.util.Patterns;
+import android.widget.Toast;
+
+public class BugreportProgressService extends Service {
+    private static final String TAG = "Shell";
+
+    private static final String AUTHORITY = "com.android.shell";
+
+    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent != null) {
+            onBugreportFinished(intent);
+        }
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private void onBugreportFinished(Intent intent) {
+        final Context context = getApplicationContext();
+        final Configuration conf = context.getResources().getConfiguration();
+        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+            triggerLocalNotification(context, bugreportFile, screenshotFile);
+        }
+        stopSelf();
+    }
+
+    /**
+     * Responsible for triggering a notification that allows the user to start a
+     * "share" intent with the bug report. On watches we have other methods to allow the user to
+     * start this intent (usually by triggering it on another connected device); we don't need to
+     * display the notification in this case.
+     */
+    private static void triggerLocalNotification(final Context context, final File bugreportFile,
+            final File screenshotFile) {
+        if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+            Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+                    Toast.LENGTH_LONG).show();
+            return;
+        }
+
+        boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
+        if (!isPlainText) {
+            // Already zipped, send it right away.
+            sendBugreportNotification(context, bugreportFile, screenshotFile);
+        } else {
+            // Asynchronously zip the file first, then send it.
+            sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+        }
+    }
+
+    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+        final Intent intent = new Intent(context, BugreportWarningActivity.class);
+        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+        return intent;
+    }
+
+    /**
+     * Build {@link Intent} that can be used to share the given bugreport.
+     */
+    private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+        final String mimeType = "application/vnd.android.bugreport";
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setType(mimeType);
+
+        intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+        // create the ClipData object with the attachments URIs.
+        String messageBody = String.format("Build info: %s\nSerial number:%s",
+                SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+        intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+        final ClipData clipData = new ClipData(null, new String[] { mimeType },
+                new ClipData.Item(null, null, null, bugreportUri));
+        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+        if (screenshotUri != null) {
+            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+            attachments.add(screenshotUri);
+        }
+        intent.setClipData(clipData);
+        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+        final Account sendToAccount = findSendToAccount(context);
+        if (sendToAccount != null) {
+            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+        }
+
+        return intent;
+    }
+
+    /**
+     * Sends a bugreport notitication.
+     */
+    private static void sendBugreportNotification(Context context, File bugreportFile,
+            File screenshotFile) {
+        // Files are kept on private storage, so turn into Uris that we can
+        // grant temporary permissions for.
+        final Uri bugreportUri = getUri(context, bugreportFile);
+        final Uri screenshotUri = getUri(context, screenshotFile);
+
+        Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+        Intent notifIntent;
+
+        // Send through warning dialog by default
+        if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+            notifIntent = buildWarningIntent(context, sendIntent);
+        } else {
+            notifIntent = sendIntent;
+        }
+        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Notification.Builder builder = new Notification.Builder(context)
+                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                .setContentTitle(context.getString(R.string.bugreport_finished_title))
+                .setTicker(context.getString(R.string.bugreport_finished_title))
+                .setContentText(context.getString(R.string.bugreport_finished_text))
+                .setContentIntent(PendingIntent.getActivity(
+                        context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
+                .setAutoCancel(true)
+                .setLocalOnly(true)
+                .setColor(context.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+
+        NotificationManager.from(context).notify(TAG, 0, builder.build());
+    }
+
+    /**
+     * Sends a zipped bugreport notification.
+     */
+    private static void sendZippedBugreportNotification(final Context context,
+            final File bugreportFile, final File screenshotFile) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                File zippedFile = zipBugreport(bugreportFile);
+                sendBugreportNotification(context, zippedFile, screenshotFile);
+                return null;
+            }
+        }.execute();
+    }
+
+    /**
+     * Zips a bugreport file, returning the path to the new file (or to the
+     * original in case of failure).
+     */
+    private static File zipBugreport(File bugreportFile) {
+        String bugreportPath = bugreportFile.getAbsolutePath();
+        String zippedPath = bugreportPath.replace(".txt", ".zip");
+        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
+        File bugreportZippedFile = new File(zippedPath);
+        try (InputStream is = new FileInputStream(bugreportFile);
+            ZipOutputStream zos = new ZipOutputStream(
+                new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+            ZipEntry entry = new ZipEntry(bugreportFile.getName());
+            entry.setTime(bugreportFile.lastModified());
+            zos.putNextEntry(entry);
+            int totalBytes = Streams.copy(is, zos);
+            Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
+            zos.closeEntry();
+            // Delete old file;
+            boolean deleted = bugreportFile.delete();
+            if (deleted) {
+                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
+            } else {
+                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
+            }
+            return bugreportZippedFile;
+        } catch (IOException e) {
+          Log.e(TAG, "exception zipping file " + zippedPath, e);
+          return bugreportFile;  // Return original.
+        }
+    }
+
+    /**
+     * Find the best matching {@link Account} based on build properties.
+     */
+    private static Account findSendToAccount(Context context) {
+        final AccountManager am = (AccountManager) context.getSystemService(
+                Context.ACCOUNT_SERVICE);
+
+        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+        if (!preferredDomain.startsWith("@")) {
+            preferredDomain = "@" + preferredDomain;
+        }
+
+        final Account[] accounts = am.getAccounts();
+        Account foundAccount = null;
+        for (Account account : accounts) {
+            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+                if (!preferredDomain.isEmpty()) {
+                    // if we have a preferred domain and it matches, return; otherwise keep
+                    // looking
+                    if (account.name.endsWith(preferredDomain)) {
+                        return account;
+                    } else {
+                        foundAccount = account;
+                    }
+                    // if we don't have a preferred domain, just return since it looks like
+                    // an email address
+                } else {
+                    return account;
+                }
+            }
+        }
+        return foundAccount;
+    }
+
+    private static Uri getUri(Context context, File file) {
+        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+    }
+
+    static File getFileExtra(Intent intent, String key) {
+        final String path = intent.getStringExtra(key);
+        if (path != null) {
+            return new File(path);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index 898e8c9..f1da14d 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -16,53 +16,23 @@
 
 package com.android.shell;
 
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.getFileExtra;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import java.io.File;
+
 import android.content.BroadcastReceiver;
-import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.FileUtils;
-import android.os.SystemProperties;
-import android.support.v4.content.FileProvider;
 import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Patterns;
-import android.widget.Toast;
-
-import com.google.android.collect.Lists;
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-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;
-import java.util.ArrayList;
 
 /**
  * Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND}.
+ * {@link Intent#ACTION_SEND_MULTIPLE}.
  */
 public class BugreportReceiver extends BroadcastReceiver {
-    private static final String TAG = "Shell";
-
-    private static final String AUTHORITY = "com.android.shell";
-
-    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
-    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
 
     /**
      * Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
@@ -77,15 +47,17 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        final Configuration conf = context.getResources().getConfiguration();
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
-
-        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
-            triggerLocalNotification(context, bugreportFile, screenshotFile);
-        }
-
         // Clean up older bugreports in background
+        cleanupOldFiles(intent);
+
+        // Delegate to service.
+        Intent serviceIntent = new Intent(context, BugreportProgressService.class);
+        serviceIntent.putExtras(intent.getExtras());
+        context.startService(serviceIntent);
+    }
+
+    private void cleanupOldFiles(Intent intent) {
+        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
         final PendingResult result = goAsync();
         new AsyncTask<Void, Void, Void>() {
             @Override
@@ -97,201 +69,4 @@
             }
         }.execute();
     }
-
-    /**
-     * Responsible for triggering a notification that allows the user to start a
-     * "share" intent with the bug report. On watches we have other methods to allow the user to
-     * start this intent (usually by triggering it on another connected device); we don't need to
-     * display the notification in this case.
-     */
-    private void triggerLocalNotification(final Context context, final File bugreportFile,
-            final File screenshotFile) {
-        if (!bugreportFile.exists() || !bugreportFile.canRead()) {
-            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
-            Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
-                    Toast.LENGTH_LONG).show();
-            return;
-        }
-
-        boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
-        if (!isPlainText) {
-            // Already zipped, send it right away.
-            sendBugreportNotification(context, bugreportFile, screenshotFile);
-        } else {
-            // Asynchronously zip the file first, then send it.
-            sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
-        }
-    }
-
-    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
-        final Intent intent = new Intent(context, BugreportWarningActivity.class);
-        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
-        return intent;
-    }
-
-    /**
-     * Build {@link Intent} that can be used to share the given bugreport.
-     */
-    private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
-        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
-        final String mimeType = "application/vnd.android.bugreport";
-        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        intent.addCategory(Intent.CATEGORY_DEFAULT);
-        intent.setType(mimeType);
-
-        intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
-
-        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
-        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
-        // create the ClipData object with the attachments URIs.
-        String messageBody = String.format("Build info: %s\nSerial number:%s",
-                SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
-        intent.putExtra(Intent.EXTRA_TEXT, messageBody);
-        final ClipData clipData = new ClipData(null, new String[] { mimeType },
-                new ClipData.Item(null, null, null, bugreportUri));
-        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
-        if (screenshotUri != null) {
-            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
-            attachments.add(screenshotUri);
-        }
-        intent.setClipData(clipData);
-        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
-
-        final Account sendToAccount = findSendToAccount(context);
-        if (sendToAccount != null) {
-            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
-        }
-
-        return intent;
-    }
-
-    /**
-     * Sends a bugreport notitication.
-     */
-    private static void sendBugreportNotification(Context context, File bugreportFile,
-            File screenshotFile) {
-        // Files are kept on private storage, so turn into Uris that we can
-        // grant temporary permissions for.
-        final Uri bugreportUri = getUri(context, bugreportFile);
-        final Uri screenshotUri = getUri(context, screenshotFile);
-
-        Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
-        Intent notifIntent;
-
-        // Send through warning dialog by default
-        if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
-            notifIntent = buildWarningIntent(context, sendIntent);
-        } else {
-            notifIntent = sendIntent;
-        }
-        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        final Notification.Builder builder = new Notification.Builder(context)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setContentTitle(context.getString(R.string.bugreport_finished_title))
-                .setTicker(context.getString(R.string.bugreport_finished_title))
-                .setContentText(context.getString(R.string.bugreport_finished_text))
-                .setContentIntent(PendingIntent.getActivity(
-                        context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
-                .setAutoCancel(true)
-                .setLocalOnly(true)
-                .setColor(context.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
-
-        NotificationManager.from(context).notify(TAG, 0, builder.build());
-    }
-
-    /**
-     * Sends a zipped bugreport notification.
-     */
-    private static void sendZippedBugreportNotification(final Context context,
-            final File bugreportFile, final File screenshotFile) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                File zippedFile = zipBugreport(bugreportFile);
-                sendBugreportNotification(context, zippedFile, screenshotFile);
-                return null;
-            }
-        }.execute();
-    }
-
-    /**
-     * Zips a bugreport file, returning the path to the new file (or to the
-     * original in case of failure).
-     */
-    private static File zipBugreport(File bugreportFile) {
-        String bugreportPath = bugreportFile.getAbsolutePath();
-        String zippedPath = bugreportPath.replace(".txt", ".zip");
-        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
-        File bugreportZippedFile = new File(zippedPath);
-        try (InputStream is = new FileInputStream(bugreportFile);
-            ZipOutputStream zos = new ZipOutputStream(
-                new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
-            ZipEntry entry = new ZipEntry(bugreportFile.getName());
-            entry.setTime(bugreportFile.lastModified());
-            zos.putNextEntry(entry);
-            int totalBytes = Streams.copy(is, zos);
-            Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
-            zos.closeEntry();
-            // Delete old file;
-            boolean deleted = bugreportFile.delete();
-            if (deleted) {
-                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
-            } else {
-                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
-            }
-            return bugreportZippedFile;
-        } catch (IOException e) {
-          Log.e(TAG, "exception zipping file " + zippedPath, e);
-          return bugreportFile;  // Return original.
-        }
-    }
-
-    /**
-     * Find the best matching {@link Account} based on build properties.
-     */
-    private static Account findSendToAccount(Context context) {
-        final AccountManager am = (AccountManager) context.getSystemService(
-                Context.ACCOUNT_SERVICE);
-
-        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
-        if (!preferredDomain.startsWith("@")) {
-            preferredDomain = "@" + preferredDomain;
-        }
-
-        final Account[] accounts = am.getAccounts();
-        Account foundAccount = null;
-        for (Account account : accounts) {
-            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
-                if (!preferredDomain.isEmpty()) {
-                    // if we have a preferred domain and it matches, return; otherwise keep
-                    // looking
-                    if (account.name.endsWith(preferredDomain)) {
-                        return account;
-                    } else {
-                        foundAccount = account;
-                    }
-                    // if we don't have a preferred domain, just return since it looks like
-                    // an email address
-                } else {
-                    return account;
-                }
-            }
-        }
-        return foundAccount;
-    }
-
-    private static Uri getUri(Context context, File file) {
-        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
-    }
-
-    private static File getFileExtra(Intent intent, String key) {
-        final String path = intent.getStringExtra(key);
-        if (path != null) {
-            return new File(path);
-        } else {
-            return null;
-        }
-    }
 }
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 06565c0..1bdd9dd 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -18,8 +18,8 @@
 
 import static android.test.MoreAsserts.assertContainsRegex;
 import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportReceiver.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportReceiver.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
 
 import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;