Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.messaging.util;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.FragmentManager;
     22 import android.app.FragmentTransaction;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.media.MediaPlayer;
     26 import android.os.Environment;
     27 import android.telephony.SmsMessage;
     28 import android.text.TextUtils;
     29 import android.widget.ArrayAdapter;
     30 
     31 import com.android.messaging.R;
     32 import com.android.messaging.datamodel.SyncManager;
     33 import com.android.messaging.datamodel.action.DumpDatabaseAction;
     34 import com.android.messaging.datamodel.action.LogTelephonyDatabaseAction;
     35 import com.android.messaging.sms.MmsUtils;
     36 import com.android.messaging.ui.UIIntents;
     37 import com.android.messaging.ui.debug.DebugSmsMmsFromDumpFileDialogFragment;
     38 import com.google.common.io.ByteStreams;
     39 
     40 import java.io.BufferedInputStream;
     41 import java.io.DataInputStream;
     42 import java.io.DataOutputStream;
     43 import java.io.File;
     44 import java.io.FileInputStream;
     45 import java.io.FileNotFoundException;
     46 import java.io.FileOutputStream;
     47 import java.io.FilenameFilter;
     48 import java.io.IOException;
     49 import java.io.StreamCorruptedException;
     50 
     51 public class DebugUtils {
     52     private static final String TAG = "bugle.util.DebugUtils";
     53 
     54     private static boolean sDebugNoise;
     55     private static boolean sDebugClassZeroSms;
     56     private static MediaPlayer [] sMediaPlayer;
     57     private static final Object sLock = new Object();
     58 
     59     public static final int DEBUG_SOUND_SERVER_REQUEST = 0;
     60     public static final int DEBUG_SOUND_DB_OP = 1;
     61 
     62     public static void maybePlayDebugNoise(final Context context, final int sound) {
     63         if (sDebugNoise) {
     64             synchronized (sLock) {
     65                 try {
     66                     if (sMediaPlayer == null) {
     67                         sMediaPlayer = new MediaPlayer[2];
     68                         sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST] =
     69                                 MediaPlayer.create(context, R.raw.server_request_debug);
     70                         sMediaPlayer[DEBUG_SOUND_DB_OP] =
     71                                 MediaPlayer.create(context, R.raw.db_op_debug);
     72                         sMediaPlayer[DEBUG_SOUND_DB_OP].setVolume(1.0F, 1.0F);
     73                         sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST].setVolume(0.3F, 0.3F);
     74                     }
     75                     if (sMediaPlayer[sound] != null) {
     76                         sMediaPlayer[sound].start();
     77                     }
     78                 } catch (final IllegalArgumentException e) {
     79                     LogUtil.e(TAG, "MediaPlayer exception", e);
     80                 } catch (final SecurityException e) {
     81                     LogUtil.e(TAG, "MediaPlayer exception", e);
     82                 } catch (final IllegalStateException e) {
     83                     LogUtil.e(TAG, "MediaPlayer exception", e);
     84                 }
     85             }
     86         }
     87     }
     88 
     89     public static boolean isDebugEnabled() {
     90         return BugleGservices.get().getBoolean(BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES,
     91                 BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES_DEFAULT);
     92     }
     93 
     94     public abstract static class DebugAction {
     95         String mTitle;
     96         public DebugAction(final String title) {
     97             mTitle = title;
     98         }
     99 
    100         @Override
    101         public String toString() {
    102             return mTitle;
    103         }
    104 
    105         public abstract void run();
    106     }
    107 
    108     public static void showDebugOptions(final Activity host) {
    109         final AlertDialog.Builder builder = new AlertDialog.Builder(host);
    110 
    111         final ArrayAdapter<DebugAction> arrayAdapter = new ArrayAdapter<DebugAction>(
    112                 host, android.R.layout.simple_list_item_1);
    113 
    114         arrayAdapter.add(new DebugAction("Dump Database") {
    115             @Override
    116             public void run() {
    117                 DumpDatabaseAction.dumpDatabase();
    118             }
    119         });
    120 
    121         arrayAdapter.add(new DebugAction("Log Telephony Data") {
    122             @Override
    123             public void run() {
    124                 LogTelephonyDatabaseAction.dumpDatabase();
    125             }
    126         });
    127 
    128         arrayAdapter.add(new DebugAction("Toggle Noise") {
    129             @Override
    130             public void run() {
    131                 sDebugNoise = !sDebugNoise;
    132             }
    133         });
    134 
    135         arrayAdapter.add(new DebugAction("Force sync SMS") {
    136             @Override
    137             public void run() {
    138                 final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
    139                 prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1);
    140                 SyncManager.forceSync();
    141             }
    142         });
    143 
    144         arrayAdapter.add(new DebugAction("Sync SMS") {
    145             @Override
    146             public void run() {
    147                 SyncManager.sync();
    148             }
    149         });
    150 
    151         arrayAdapter.add(new DebugAction("Load SMS/MMS from dump file") {
    152             @Override
    153             public void run() {
    154                 new DebugSmsMmsDumpTask(host,
    155                         DebugSmsMmsFromDumpFileDialogFragment.ACTION_LOAD).executeOnThreadPool();
    156             }
    157         });
    158 
    159         arrayAdapter.add(new DebugAction("Email SMS/MMS dump file") {
    160             @Override
    161             public void run() {
    162                 new DebugSmsMmsDumpTask(host,
    163                         DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL).executeOnThreadPool();
    164             }
    165         });
    166 
    167         arrayAdapter.add(new DebugAction("MMS Config...") {
    168             @Override
    169             public void run() {
    170                 UIIntents.get().launchDebugMmsConfigActivity(host);
    171             }
    172         });
    173 
    174         arrayAdapter.add(new DebugAction(sDebugClassZeroSms ? "Turn off Class 0 sms test" :
    175                 "Turn on Class Zero test") {
    176             @Override
    177             public void run() {
    178                 sDebugClassZeroSms = !sDebugClassZeroSms;
    179             }
    180         });
    181 
    182         builder.setAdapter(arrayAdapter,
    183                 new android.content.DialogInterface.OnClickListener() {
    184             @Override
    185             public void onClick(final DialogInterface arg0, final int pos) {
    186                 arrayAdapter.getItem(pos).run();
    187             }
    188         });
    189 
    190         builder.create().show();
    191     }
    192 
    193     /**
    194      * Task to list all the dump files and perform an action on it
    195      */
    196     private static class DebugSmsMmsDumpTask extends SafeAsyncTask<Void, Void, String[]> {
    197         private final String mAction;
    198         private final Activity mHost;
    199 
    200         public DebugSmsMmsDumpTask(final Activity host, final String action) {
    201             mHost = host;
    202             mAction = action;
    203         }
    204 
    205         @Override
    206         protected void onPostExecute(final String[] result) {
    207             if (result == null || result.length < 1) {
    208                 return;
    209             }
    210             final FragmentManager fragmentManager = mHost.getFragmentManager();
    211             final FragmentTransaction ft = fragmentManager.beginTransaction();
    212             final DebugSmsMmsFromDumpFileDialogFragment dialog =
    213                     DebugSmsMmsFromDumpFileDialogFragment.newInstance(result, mAction);
    214             dialog.show(fragmentManager, ""/*tag*/);
    215         }
    216 
    217         @Override
    218         protected String[] doInBackgroundTimed(final Void... params) {
    219             final File dir = DebugUtils.getDebugFilesDir();
    220             return dir.list(new FilenameFilter() {
    221                 @Override
    222                 public boolean accept(final File dir, final String filename) {
    223                     return filename != null
    224                             && ((mAction == DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL
    225                             && filename.equals(DumpDatabaseAction.DUMP_NAME))
    226                             || filename.startsWith(MmsUtils.MMS_DUMP_PREFIX)
    227                             || filename.startsWith(MmsUtils.SMS_DUMP_PREFIX));
    228                 }
    229             });
    230         }
    231     }
    232 
    233     /**
    234      * Dump the received raw SMS data into a file on external storage
    235      *
    236      * @param id The ID to use as part of the dump file name
    237      * @param messages The raw SMS data
    238      */
    239     public static void dumpSms(final long id, final android.telephony.SmsMessage[] messages,
    240             final String format) {
    241         try {
    242             final String dumpFileName = MmsUtils.SMS_DUMP_PREFIX + Long.toString(id);
    243             final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
    244             if (dumpFile != null) {
    245                 final FileOutputStream fos = new FileOutputStream(dumpFile);
    246                 final DataOutputStream dos = new DataOutputStream(fos);
    247                 try {
    248                     final int chars = (TextUtils.isEmpty(format) ? 0 : format.length());
    249                     dos.writeInt(chars);
    250                     if (chars > 0) {
    251                         dos.writeUTF(format);
    252                     }
    253                     dos.writeInt(messages.length);
    254                     for (final android.telephony.SmsMessage message : messages) {
    255                         final byte[] pdu = message.getPdu();
    256                         dos.writeInt(pdu.length);
    257                         dos.write(pdu, 0, pdu.length);
    258                     }
    259                     dos.flush();
    260                 } finally {
    261                     dos.close();
    262                     ensureReadable(dumpFile);
    263                 }
    264             }
    265         } catch (final IOException e) {
    266             LogUtil.e(LogUtil.BUGLE_TAG, "dumpSms: " + e, e);
    267         }
    268     }
    269 
    270     /**
    271      * Load MMS/SMS from the dump file
    272      */
    273     public static SmsMessage[] retreiveSmsFromDumpFile(final String dumpFileName) {
    274         SmsMessage[] messages = null;
    275         final File inputFile = DebugUtils.getDebugFile(dumpFileName, false);
    276         if (inputFile != null) {
    277             FileInputStream fis = null;
    278             DataInputStream dis = null;
    279             try {
    280                 fis = new FileInputStream(inputFile);
    281                 dis = new DataInputStream(fis);
    282 
    283                 // SMS dump
    284                 final int chars = dis.readInt();
    285                 if (chars > 0) {
    286                     final String format = dis.readUTF();
    287                 }
    288                 final int count = dis.readInt();
    289                 final SmsMessage[] messagesTemp = new SmsMessage[count];
    290                 for (int i = 0; i < count; i++) {
    291                     final int length = dis.readInt();
    292                     final byte[] pdu = new byte[length];
    293                     dis.read(pdu, 0, length);
    294                     messagesTemp[i] = SmsMessage.createFromPdu(pdu);
    295                 }
    296                 messages = messagesTemp;
    297             } catch (final FileNotFoundException e) {
    298                 // Nothing to do
    299             } catch (final StreamCorruptedException e) {
    300                 // Nothing to do
    301             } catch (final IOException e) {
    302                 // Nothing to do
    303             } finally {
    304                 if (dis != null) {
    305                     try {
    306                         dis.close();
    307                     } catch (final IOException e) {
    308                         // Nothing to do
    309                     }
    310                 }
    311             }
    312         }
    313         return messages;
    314     }
    315 
    316     public static File getDebugFile(final String fileName, final boolean create) {
    317         final File dir = getDebugFilesDir();
    318         final File file = new File(dir, fileName);
    319         if (create && file.exists()) {
    320             file.delete();
    321         }
    322         return file;
    323     }
    324 
    325     public static File getDebugFilesDir() {
    326         final File dir = Environment.getExternalStorageDirectory();
    327         return dir;
    328     }
    329 
    330     /**
    331      * Load MMS/SMS from the dump file
    332      */
    333     public static byte[] receiveFromDumpFile(final String dumpFileName) {
    334         byte[] data = null;
    335         try {
    336             final File inputFile = getDebugFile(dumpFileName, false);
    337             if (inputFile != null) {
    338                 final FileInputStream fis = new FileInputStream(inputFile);
    339                 final BufferedInputStream bis = new BufferedInputStream(fis);
    340                 try {
    341                     // dump file
    342                     data = ByteStreams.toByteArray(bis);
    343                     if (data == null || data.length < 1) {
    344                         LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: empty data");
    345                     }
    346                 } finally {
    347                     bis.close();
    348                 }
    349             }
    350         } catch (final IOException e) {
    351             LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: " + e, e);
    352         }
    353         return data;
    354     }
    355 
    356     public static void ensureReadable(final File file) {
    357         if (file.exists()){
    358             file.setReadable(true, false);
    359         }
    360     }
    361 
    362     /**
    363      * Logs the name of the method that is currently executing, e.g. "MyActivity.onCreate". This is
    364      * useful for surgically adding logs for tracing execution while debugging.
    365      * <p>
    366      * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
    367      * However, this method is only executed on eng builds if DEBUG logs are loggable.
    368      */
    369     public static void logCurrentMethod(String tag) {
    370         if (!LogUtil.isLoggable(tag, LogUtil.DEBUG)) {
    371             return;
    372         }
    373         StackTraceElement caller = getCaller(1);
    374         if (caller == null) {
    375             return;
    376         }
    377         String className = caller.getClassName();
    378         // Strip off the package name
    379         int lastDot = className.lastIndexOf('.');
    380         if (lastDot > -1) {
    381             className = className.substring(lastDot + 1);
    382         }
    383         LogUtil.d(tag, className + "." + caller.getMethodName());
    384     }
    385 
    386     /**
    387      * Returns info about the calling method. The {@code depth} parameter controls how far back to
    388      * go. For example, if foo() calls bar(), and bar() calls getCaller(0), it returns info about
    389      * bar(). If bar() instead called getCaller(1), it would return info about foo(). And so on.
    390      * <p>
    391      * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
    392      * It should only be used in production where necessary to gather context about an error or
    393      * unexpected event (e.g. the {@link Assert} class uses it).
    394      *
    395      * @return stack frame information for the caller (if found); otherwise {@code null}.
    396      */
    397     public static StackTraceElement getCaller(int depth) {
    398         // If the signature of this method is changed, proguard.flags must be updated!
    399         if (depth < 0) {
    400             throw new IllegalArgumentException("depth cannot be negative");
    401         }
    402         StackTraceElement[] trace = Thread.currentThread().getStackTrace();
    403         if (trace == null || trace.length < (depth + 2)) {
    404             return null;
    405         }
    406         // The stack trace includes some methods we don't care about (e.g. this method).
    407         // Walk down until we find this method, and then back up to the caller we're looking for.
    408         for (int i = 0; i < trace.length - 1; i++) {
    409             String methodName = trace[i].getMethodName();
    410             if ("getCaller".equals(methodName)) {
    411                 return trace[i + depth + 1];
    412             }
    413         }
    414         // Never found ourself in the stack?!
    415         return null;
    416     }
    417 
    418     /**
    419      * Returns a boolean indicating whether ClassZero debugging is enabled. If enabled, any received
    420      * sms is treated as if it were a class zero message and displayed by the ClassZeroActivity.
    421      */
    422     public static boolean debugClassZeroSmsEnabled() {
    423         return sDebugClassZeroSms;
    424     }
    425 }
    426