Home | History | Annotate | Download | only in dumpviewer
      1 /*
      2  * Copyright (C) 2018 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 package com.android.dumpviewer;
     17 
     18 import android.app.Activity;
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.SharedPreferences;
     23 import android.os.AsyncTask;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.provider.Settings;
     27 import android.provider.Settings.Global;
     28 import android.text.Editable;
     29 import android.text.TextWatcher;
     30 import android.util.Log;
     31 import android.view.KeyEvent;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.view.inputmethod.InputMethodManager;
     35 import android.webkit.WebView;
     36 import android.webkit.WebViewClient;
     37 import android.widget.ArrayAdapter;
     38 import android.widget.AutoCompleteTextView;
     39 import android.widget.Button;
     40 import android.widget.CheckBox;
     41 import android.widget.EditText;
     42 import android.widget.TextView;
     43 
     44 import com.android.dumpviewer.R.id;
     45 import com.android.dumpviewer.pickers.PackageNamePicker;
     46 import com.android.dumpviewer.pickers.PickerActivity;
     47 import com.android.dumpviewer.pickers.ProcessNamePicker;
     48 import com.android.dumpviewer.utils.Exec;
     49 import com.android.dumpviewer.utils.GrepHelper;
     50 import com.android.dumpviewer.utils.History;
     51 import com.android.dumpviewer.utils.Utils;
     52 
     53 import java.io.ByteArrayOutputStream;
     54 import java.io.InputStream;
     55 import java.util.ArrayList;
     56 import java.util.Arrays;
     57 import java.util.List;
     58 import java.util.concurrent.atomic.AtomicBoolean;
     59 import java.util.regex.Pattern;
     60 
     61 import androidx.annotation.Nullable;
     62 import androidx.appcompat.app.AlertDialog;
     63 import androidx.appcompat.app.AppCompatActivity;
     64 
     65 public class DumpActivity extends AppCompatActivity {
     66     public static final String TAG = "DumpViewer";
     67 
     68     private static final int MAX_HISTORY_SIZE = 32;
     69     private static final String SHARED_PREF_NAME = "prefs";
     70 
     71     private static final int MAX_RESULT_SIZE = 1 * 1024 * 1024;
     72 
     73     private static final int CODE_MULTI_PICKER = 1;
     74 
     75     private final Handler mHandler = new Handler();
     76 
     77     private WebView mWebView;
     78     private AutoCompleteTextView mAcCommandLine;
     79     private AutoCompleteTextView mAcBeforeContext;
     80     private AutoCompleteTextView mAcAfterContext;
     81     private AutoCompleteTextView mAcHead;
     82     private AutoCompleteTextView mAcTail;
     83     private AutoCompleteTextView mAcPattern;
     84     private AutoCompleteTextView mAcSearchQuery;
     85     private CheckBox mIgnoreCaseGrep;
     86     private CheckBox mShowLast;
     87 
     88     private Button mExecuteButton;
     89     private Button mNextButton;
     90     private Button mPrevButton;
     91 
     92     private Button mOpenButton;
     93     private Button mCloseButton;
     94 
     95     private Button mMultiPickerButton;
     96     private Button mRePickerButton;
     97 
     98     private ViewGroup mHeader1;
     99 
    100     private AsyncTask<Void, Void, String> mRunningTask;
    101 
    102     private SharedPreferences mPrefs;
    103     private History mCommandHistory;
    104     private History mRegexpHistory;
    105     private History mSearchHistory;
    106 
    107     private long mLastCollapseTime;
    108 
    109     private GrepHelper mGrepHelper;
    110 
    111     private static final List<String> DEFAULT_COMMANDS = Arrays.asList(new String[]{
    112             "dumpsys activity",
    113             "dumpsys activity activities",
    114             "dumpsys activity broadcasts",
    115             "dumpsys activity broadcasts history",
    116             "dumpsys activity services",
    117             "dumpsys activity starter",
    118             "dumpsys activity processes",
    119             "dumpsys activity recents",
    120             "dumpsys activity lastanr-traces",
    121             "dumpsys alarm",
    122             "dumpsys appops",
    123             "dumpsys backup",
    124             "dumpsys battery",
    125             "dumpsys bluetooth_manager",
    126             "dumpsys content",
    127             "dumpsys deviceidle",
    128             "dumpsys device_policy",
    129             "dumpsys jobscheduler",
    130             "dumpsys location",
    131             "dumpsys meminfo -a",
    132             "dumpsys netpolicy",
    133             "dumpsys notification",
    134             "dumpsys package",
    135             "dumpsys power",
    136             "dumpsys procstats",
    137             "dumpsys settings",
    138             "dumpsys shortcut",
    139             "dumpsys usagestats",
    140             "dumpsys user",
    141 
    142             "dumpsys activity service com.android.systemui/.SystemUIService",
    143             "dumpsys activity provider com.android.providers.contacts/.ContactsProvider2",
    144             "dumpsys activity provider com.android.providers.contacts/.CallLogProvider",
    145             "dumpsys activity provider com.android.providers.calendar.CalendarProvider2",
    146 
    147             "logcat -v uid -b main",
    148             "logcat -v uid -b all",
    149             "logcat -v uid -b system",
    150             "logcat -v uid -b crash",
    151             "logcat -v uid -b radio",
    152             "logcat -v uid -b events"
    153     });
    154 
    155     private InputMethodManager mImm;
    156 
    157     private EditText mLastFocusedEditBox;
    158 
    159     private boolean mDoScrollWebView;
    160 
    161     @Override
    162     protected void onCreate(Bundle savedInstanceState) {
    163         super.onCreate(savedInstanceState);
    164         setContentView(R.layout.activity_dump);
    165 
    166         mGrepHelper = GrepHelper.getHelper();
    167 
    168         mImm = getSystemService(InputMethodManager.class);
    169 
    170         ((TextView) findViewById(R.id.grep_label)).setText(mGrepHelper.getCommandName());
    171 
    172         mWebView = findViewById(R.id.webview);
    173         mWebView.getSettings().setBuiltInZoomControls(true);
    174         mWebView.getSettings().setLoadWithOverviewMode(true);
    175 
    176         mHeader1 = findViewById(R.id.header1);
    177 
    178         mExecuteButton = findViewById(R.id.start);
    179         mExecuteButton.setOnClickListener(this::onStartClicked);
    180         mNextButton = findViewById(R.id.find_next);
    181         mNextButton.setOnClickListener(this::onFindNextClicked);
    182         mPrevButton = findViewById(R.id.find_prev);
    183         mPrevButton.setOnClickListener(this::onFindPrevClicked);
    184 
    185         mOpenButton = findViewById(R.id.open_header);
    186         mOpenButton.setOnClickListener(this::onOpenHeaderClicked);
    187         mCloseButton = findViewById(R.id.close_header);
    188         mCloseButton.setOnClickListener(this::onCloseHeaderClicked);
    189 
    190         mMultiPickerButton = findViewById(id.multi_picker);
    191         mMultiPickerButton.setOnClickListener(this::onMultiPickerClicked);
    192         mRePickerButton = findViewById(id.re_picker);
    193         mRePickerButton.setOnClickListener(this::onRePickerClicked);
    194 
    195         mAcCommandLine = findViewById(R.id.commandline);
    196         mAcAfterContext = findViewById(R.id.afterContext);
    197         mAcBeforeContext = findViewById(R.id.beforeContext);
    198         mAcHead = findViewById(R.id.head);
    199         mAcTail = findViewById(R.id.tail);
    200         mAcPattern = findViewById(R.id.pattern);
    201         mAcSearchQuery = findViewById(R.id.search);
    202 
    203         mIgnoreCaseGrep = findViewById(R.id.ignore_case);
    204         mShowLast = findViewById(R.id.scroll_to_bottm);
    205 
    206         mPrefs = getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
    207         mCommandHistory = new History(mPrefs, "command_history", MAX_HISTORY_SIZE);
    208         mCommandHistory.load();
    209         mRegexpHistory = new History(mPrefs, "regexp_history", MAX_HISTORY_SIZE);
    210         mRegexpHistory.load();
    211         mSearchHistory = new History(mPrefs, "search_history", MAX_HISTORY_SIZE);
    212         mSearchHistory.load();
    213 
    214         setupAutocomplete(mAcBeforeContext, "0", "1", "2", "3", "5", "10");
    215         setupAutocomplete(mAcAfterContext, "0", "1", "2", "3", "5", "10");
    216         setupAutocomplete(mAcHead, "0", "100", "1000", "2000");
    217         setupAutocomplete(mAcTail, "0", "100", "1000", "2000");
    218 
    219         mAcCommandLine.setOnKeyListener(this::onAutocompleteKey);
    220         mAcPattern.setOnKeyListener(this::onAutocompleteKey);
    221         mAcSearchQuery.setOnKeyListener(this::onAutocompleteKey);
    222 
    223         mWebView.setWebViewClient(new WebViewClient() {
    224             @Override
    225             public void onPageFinished(WebView view, String url) {
    226                 // Apparently we need a small delay for it to work.
    227                 mHandler.postDelayed(DumpActivity.this::onContentLoaded, 200);
    228             }
    229         });
    230         refreshHistory();
    231 
    232         loadSharePrefs();
    233 
    234         refreshUi();
    235     }
    236 
    237     private void refreshUi() {
    238         final boolean canExecute = getCommandLine().length() > 0;
    239         final boolean canSearch = mAcSearchQuery.getText().length() > 0;
    240 
    241         mExecuteButton.setEnabled(canExecute);
    242         mNextButton.setEnabled(canSearch);
    243         mPrevButton.setEnabled(canSearch);
    244 
    245         if (mHeader1.getVisibility() == View.VISIBLE) {
    246             mCloseButton.setVisibility(View.VISIBLE);
    247             mOpenButton.setVisibility(View.GONE);
    248         } else {
    249             mOpenButton.setVisibility(View.VISIBLE);
    250             mCloseButton.setVisibility(View.GONE);
    251         }
    252     }
    253 
    254     private void saveSharePrefs() {
    255     }
    256 
    257     private void loadSharePrefs() {
    258     }
    259 
    260     @Override
    261     protected void onPause() {
    262         saveSharePrefs();
    263         super.onPause();
    264     }
    265 
    266     @Override
    267     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    268         if (requestCode == CODE_MULTI_PICKER) {
    269             if (resultCode == Activity.RESULT_OK) {
    270                 insertPickedString(PickerActivity.getSelectedString(data));
    271             }
    272             return;
    273         }
    274         super.onActivityResult(requestCode, resultCode, data);
    275     }
    276 
    277     private void setupAutocomplete(AutoCompleteTextView target, List<String> values) {
    278         setupAutocomplete(target, values.toArray(new String[values.size()]));
    279     }
    280 
    281     private void setupAutocomplete(AutoCompleteTextView target, String... values) {
    282         final ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
    283                 R.layout.dropdown_item_1, values);
    284         target.setAdapter(adapter);
    285         target.setOnClickListener((v) -> ((AutoCompleteTextView) v).showDropDown());
    286         target.setOnFocusChangeListener(this::onAutocompleteFocusChanged);
    287         target.addTextChangedListener(
    288                 new TextWatcher() {
    289                     @Override
    290                     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    291                     }
    292 
    293                     @Override
    294                     public void onTextChanged(CharSequence s, int start, int before, int count) {
    295                     }
    296 
    297                     @Override
    298                     public void afterTextChanged(Editable s) {
    299                         refreshUi();
    300                     }
    301                 });
    302     }
    303 
    304     public boolean onAutocompleteKey(View view, int keyCode, KeyEvent keyevent) {
    305         if (keyevent.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
    306             if (view == mAcSearchQuery) {
    307                 doFindNextOrPrev(true);
    308             } else {
    309                 doStartCommand();
    310             }
    311             return true;
    312         }
    313         return false;
    314     }
    315 
    316     private void onAutocompleteFocusChanged(View v, boolean hasFocus) {
    317         if ((System.currentTimeMillis() - mLastCollapseTime) < 300) {
    318             // Hack: We don't want to open the pop up because the focus changed because of
    319             // collapsing, so we suppress it.
    320             return;
    321         }
    322         if (hasFocus) {
    323             if (v == mAcCommandLine || v == mAcPattern || v == mAcSearchQuery) {
    324                 mLastFocusedEditBox = (EditText) v;
    325             }
    326             final AutoCompleteTextView target = (AutoCompleteTextView) v;
    327             Utils.sMainHandler.post(() -> {
    328                 if (!isDestroyed() && !target.isPopupShowing()) {
    329                     try {
    330                         target.showDropDown();
    331                     } catch (Exception e) {
    332                     }
    333                 }
    334             });
    335         }
    336     }
    337 
    338     private void insertPickedString(String s) {
    339         insertText((mLastFocusedEditBox != null ? mLastFocusedEditBox :  mAcCommandLine), s);
    340     }
    341 
    342     private void hideIme() {
    343         mImm.hideSoftInputFromWindow(mAcCommandLine.getWindowToken(), 0);
    344     }
    345 
    346     private void refreshHistory() {
    347         // Command line autocomplete.
    348         final List<String> commands = new ArrayList<>(128);
    349         mCommandHistory.addAllTo(commands);
    350         commands.addAll(DEFAULT_COMMANDS);
    351 
    352         setupAutocomplete(mAcCommandLine, commands);
    353 
    354         // Regexp autocomplete
    355         final List<String> patterns = new ArrayList<>(MAX_HISTORY_SIZE);
    356         mRegexpHistory.addAllTo(patterns);
    357         setupAutocomplete(mAcPattern, patterns);
    358 
    359         // Search autocomplete
    360         final List<String> queries = new ArrayList<>(MAX_HISTORY_SIZE);
    361         mSearchHistory.addAllTo(queries);
    362         setupAutocomplete(mAcSearchQuery, queries);
    363     }
    364 
    365     private String getCommandLine() {
    366         return mAcCommandLine.getText().toString().trim();
    367     }
    368 
    369     private void setText(String format, Object... args) {
    370         mHandler.post(() -> setText(String.format(format, args)));
    371     }
    372 
    373     private void setText(String text) {
    374         Log.v(TAG, "Trying to set string to webview: length=" + text.length());
    375         mHandler.post(() -> {
    376             // TODO Don't do it on the main thread.
    377             final StringBuilder sb = new StringBuilder(text.length() * 2);
    378             sb.append("<html><body");
    379             sb.append(" style=\"white-space: nowrap;\"");
    380             sb.append("><pre>\n");
    381             char c;
    382             for (int i = 0; i < text.length(); i++) {
    383                 c = text.charAt(i);
    384                 switch (c) {
    385                     case '\n':
    386                         sb.append("<br>");
    387                         break;
    388                     case '<':
    389                         sb.append("&lt;");
    390                         break;
    391                     case '>':
    392                         sb.append("&gt;");
    393                         break;
    394                     case '&':
    395                         sb.append("&amp;");
    396                         break;
    397                     case '\'':
    398                         sb.append("&#39;");
    399                         break;
    400                     case '"':
    401                         sb.append("&quot;");
    402                         break;
    403                     case '#':
    404                         sb.append("%23");
    405                         break;
    406                     default:
    407                         sb.append(c);
    408                 }
    409             }
    410             sb.append("</pre></body></html>\n");
    411 
    412             mWebView.loadData(sb.toString(), "text/html", null);
    413         });
    414     }
    415 
    416     private void insertText(EditText edit, String value) {
    417         final int start = Math.max(edit.getSelectionStart(), 0);
    418         final int end = Math.max(edit.getSelectionEnd(), 0);
    419         edit.getText().replace(Math.min(start, end), Math.max(start, end),
    420                 value, 0, value.length());
    421     }
    422 
    423     private void onContentLoaded() {
    424         if (!mDoScrollWebView) {
    425             return;
    426         }
    427         mDoScrollWebView = false;
    428         if (mShowLast == null) {
    429             return;
    430         }
    431         if (mShowLast.isChecked()) {
    432             mWebView.pageDown(true /* toBottom */);
    433         } else {
    434             mWebView.pageUp(true /* toTop */);
    435         }
    436     }
    437 
    438     public void onFindNextClicked(View v) {
    439         doFindNextOrPrev(true);
    440     }
    441 
    442     public void onFindPrevClicked(View v) {
    443         doFindNextOrPrev(false);
    444     }
    445 
    446     private void onOpenHeaderClicked(View v) {
    447         toggleHeader();
    448     }
    449 
    450     private void onCloseHeaderClicked(View v) {
    451         mLastCollapseTime = System.currentTimeMillis();
    452         toggleHeader();
    453     }
    454 
    455     private void toggleHeader() {
    456         mHeader1.setVisibility(mHeader1.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
    457         refreshUi();
    458     }
    459 
    460     private void onRePickerClicked(View view) {
    461         showRegexpPicker();
    462     }
    463 
    464     private void onMultiPickerClicked(View view) {
    465         showMultiPicker();
    466     }
    467 
    468     String mLastQuery;
    469 
    470     private void doFindNextOrPrev(boolean next) {
    471         final String query = mAcSearchQuery.getText().toString();
    472         if (query.length() == 0) {
    473             return;
    474         }
    475         hideIme();
    476 
    477         mSearchHistory.add(query);
    478 
    479         if (query.equals(mLastQuery)) {
    480             mWebView.findNext(next);
    481         } else {
    482             mWebView.findAllAsync(query);
    483         }
    484         mLastQuery = query;
    485     }
    486 
    487     public void onStartClicked(View v) {
    488         doStartCommand();
    489     }
    490 
    491     public void doStartCommand() {
    492         if (mRunningTask != null) {
    493             mRunningTask.cancel(true);
    494         }
    495         final String command = getCommandLine();
    496         if (command.length() > 0) {
    497             startCommand(command);
    498         }
    499     }
    500 
    501     private void startCommand(String command) {
    502         hideIme();
    503 
    504         mCommandHistory.add(command);
    505         mRegexpHistory.add(mAcPattern.getText().toString().trim());
    506         (mRunningTask = new Dumper(command)).execute();
    507         refreshHistory();
    508     }
    509 
    510     private class Dumper extends AsyncTask<Void, Void, String> {
    511         final String command;
    512         final AtomicBoolean mTimedOut = new AtomicBoolean();
    513 
    514         public Dumper(String command) {
    515             this.command = command;
    516         }
    517 
    518         @Override
    519         protected String doInBackground(Void... voids) {
    520             if (Settings.Global.getInt(getContentResolver(), Global.ADB_ENABLED, 0) != 1) {
    521                 return "Please enable ADB (aka \"USB Debugging\" in developer options)";
    522             }
    523 
    524             final ByteArrayOutputStream out = new ByteArrayOutputStream(1024 * 1024);
    525             try {
    526                 try (InputStream is = Exec.runForStream(
    527                         buildCommandLine(command),
    528                         DumpActivity.this::setText,
    529                         () -> mTimedOut.set(true),
    530                         (e) -> {throw new RuntimeException(e.getMessage(), e);},
    531                         30)) {
    532                     final byte[] buf = new byte[1024 * 16];
    533                     int read;
    534                     int written = 0;
    535                     while ((read = is.read(buf)) >= 0) {
    536                         out.write(buf, 0, read);
    537                         written += read;
    538                         if (written >= MAX_RESULT_SIZE) {
    539                             out.write("\n[Result too long; omitted]".getBytes());
    540                             break;
    541                         }
    542                     }
    543                 }
    544             } catch (Exception e) {
    545                 if (mTimedOut.get()) {
    546                     setText("Command timed out");
    547                 } else {
    548                     setText("Caught exception: %s\n%s", e.getMessage(),
    549                             Log.getStackTraceString(e));
    550                 }
    551                 return null;
    552             }
    553 
    554             return out.toString();
    555         }
    556 
    557         @Override
    558         protected void onCancelled(String s) {
    559             mRunningTask = null;
    560         }
    561 
    562         @Override
    563         protected void onPostExecute(String s) {
    564             mRunningTask = null;
    565             if (s != null) {
    566                 if (s.length() == 0) {
    567                     setText("[No result]");
    568                 } else {
    569                     mDoScrollWebView = true;
    570                     setText(s);
    571                 }
    572             }
    573         }
    574 
    575     }
    576 
    577     private static final Pattern sLogcat = Pattern.compile("^logcat(\\s|$)");
    578 
    579     private String buildCommandLine(String command) {
    580         final StringBuilder sb = new StringBuilder(128);
    581         if (sLogcat.matcher(command).find()) {
    582             // Make sure logcat command always has -d.
    583             sb.append("logcat -d ");
    584             sb.append(command.substring(7));
    585         } else {
    586             sb.append(command);
    587         }
    588 
    589         final int before = Utils.parseInt(mAcBeforeContext.getText().toString(), 0);
    590         final int after = Utils.parseInt(mAcAfterContext.getText().toString(), 0);
    591         final int head = Utils.parseInt(mAcHead.getText().toString(), 0);
    592         final int tail = Utils.parseInt(mAcTail.getText().toString(), 0);
    593 
    594         // Don't trim regexp. Sometimes you want to search for spaces.
    595         final String regexp = mAcPattern.getText().toString();
    596         final boolean ignoreCase = mIgnoreCaseGrep.isChecked();
    597 
    598         if (regexp.length() > 0) {
    599             sb.append(" | ");
    600             mGrepHelper.buildCommand(sb, regexp, before, after, ignoreCase);
    601         }
    602         if (head > 0) {
    603             sb.append(" | head -n ");
    604             sb.append(head);
    605         }
    606         if (tail > 0) {
    607             sb.append(" | tail -n ");
    608             sb.append(tail);
    609         }
    610         sb.append(" 2>&1");
    611         return sb.toString();
    612     }
    613 
    614     // Show regex picker
    615     private void showRegexpPicker() {
    616         AlertDialog.Builder builderSingle = new AlertDialog.Builder(this);
    617         builderSingle.setTitle("Insert meta character");
    618 
    619         final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,
    620                 android.R.layout.select_dialog_item, mGrepHelper.getMetaCharacters());
    621 
    622         builderSingle.setNegativeButton("cancel", (dialog, which) -> {
    623             dialog.dismiss();
    624         });
    625 
    626         builderSingle.setAdapter(arrayAdapter, (dialog, which) -> {
    627             final String item = arrayAdapter.getItem(which);
    628             // Only use the first token
    629             final String[] vals = item.split(" ");
    630 
    631             insertText(mAcPattern, vals[0]);
    632             dialog.dismiss();
    633         });
    634         builderSingle.show();
    635     }
    636 
    637     private static final String[] sMultiPickerTargets = {
    638             "Package name",
    639 //            "Process name", // Not implemented yet.
    640     };
    641 
    642     private void showMultiPicker() {
    643         AlertDialog.Builder builderSingle = new AlertDialog.Builder(this);
    644         builderSingle.setTitle("Find and insert...");
    645 
    646         final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,
    647                 android.R.layout.select_dialog_item, sMultiPickerTargets);
    648 
    649         builderSingle.setNegativeButton("cancel", (dialog, which) -> {
    650             dialog.dismiss();
    651         });
    652 
    653         builderSingle.setAdapter(arrayAdapter, (dialog, which) -> {
    654             Class<?> activity;
    655             switch (which) {
    656                 case 0:
    657                     activity = PackageNamePicker.class;
    658                     break;
    659                 case 1:
    660                     activity = ProcessNamePicker.class;
    661                     break;
    662                 default:
    663                     throw new RuntimeException("BUG: Unknown item selected");
    664             }
    665             final Intent i = new Intent().setComponent(new ComponentName(this, activity));
    666             startActivityForResult(i, CODE_MULTI_PICKER);
    667 
    668             dialog.dismiss();
    669         });
    670         builderSingle.show();
    671     }
    672 }
    673