Home | History | Annotate | Download | only in activity
      1 /*
      2  * Copyright (C) 2016 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.googlecode.android_scripting.activity;
     18 
     19 import android.app.ListActivity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.os.Bundle;
     23 import android.text.ClipboardManager;
     24 import android.util.TypedValue;
     25 import android.view.Menu;
     26 import android.view.MenuItem;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.widget.BaseAdapter;
     30 import android.widget.TextView;
     31 import android.widget.Toast;
     32 
     33 import com.googlecode.android_scripting.ActivityFlinger;
     34 import com.googlecode.android_scripting.Log;
     35 import com.googlecode.android_scripting.Process;
     36 import com.googlecode.android_scripting.R;
     37 
     38 import java.io.BufferedReader;
     39 import java.io.File;
     40 import java.io.IOException;
     41 import java.io.InputStreamReader;
     42 import java.util.LinkedList;
     43 import java.util.List;
     44 
     45 public class LogcatViewer extends ListActivity {
     46 
     47   private List<String> mLogcatMessages;
     48   private int mOldLastPosition;
     49   private LogcatViewerAdapter mAdapter;
     50   private Process mLogcatProcess;
     51 
     52   private static enum MenuId {
     53     HELP, PREFERENCES, JUMP_TO_BOTTOM, SHARE, COPY;
     54     public int getId() {
     55       return ordinal() + Menu.FIRST;
     56     }
     57   }
     58 
     59   private class LogcatWatcher implements Runnable {
     60     @Override
     61     public void run() {
     62       mLogcatProcess = new Process();
     63       mLogcatProcess.setBinary(new File("/system/bin/logcat"));
     64       mLogcatProcess.start(null);
     65       try {
     66         BufferedReader br = new BufferedReader(new InputStreamReader(mLogcatProcess.getIn()));
     67         while (true) {
     68           final String line = br.readLine();
     69           if (line == null) {
     70             break;
     71           }
     72           runOnUiThread(new Runnable() {
     73             @Override
     74             public void run() {
     75               mLogcatMessages.add(line);
     76               mAdapter.notifyDataSetInvalidated();
     77               // This logic performs what transcriptMode="normal" should do. Since that doesn't seem
     78               // to work, we do it this way.
     79               int lastVisiblePosition = getListView().getLastVisiblePosition();
     80               int lastPosition = mLogcatMessages.size() - 1;
     81               if (lastVisiblePosition == mOldLastPosition || lastVisiblePosition == -1) {
     82                 getListView().setSelection(lastPosition);
     83               }
     84               mOldLastPosition = lastPosition;
     85             }
     86           });
     87         }
     88       } catch (IOException e) {
     89         Log.e("Failed to read from logcat process.", e);
     90       } finally {
     91         mLogcatProcess.kill();
     92       }
     93     }
     94   }
     95 
     96   @Override
     97   protected void onCreate(Bundle savedInstanceState) {
     98     super.onCreate(savedInstanceState);
     99     CustomizeWindow.requestCustomTitle(this, "Logcat", R.layout.logcat_viewer);
    100     mLogcatMessages = new LinkedList<String>();
    101     mOldLastPosition = 0;
    102     mAdapter = new LogcatViewerAdapter();
    103     setListAdapter(mAdapter);
    104     ActivityFlinger.attachView(getListView(), this);
    105     ActivityFlinger.attachView(getWindow().getDecorView(), this);
    106   }
    107 
    108   @Override
    109   public boolean onCreateOptionsMenu(Menu menu) {
    110     menu.add(Menu.NONE, MenuId.PREFERENCES.getId(), Menu.NONE, "Preferences").setIcon(
    111         android.R.drawable.ic_menu_preferences);
    112     menu.add(Menu.NONE, MenuId.JUMP_TO_BOTTOM.getId(), Menu.NONE, "Jump to Bottom").setIcon(
    113         android.R.drawable.ic_menu_revert);
    114     menu.add(Menu.NONE, MenuId.SHARE.getId(), Menu.NONE, "Share").setIcon(
    115         android.R.drawable.ic_menu_share);
    116     menu.add(Menu.NONE, MenuId.COPY.getId(), Menu.NONE, "Copy").setIcon(
    117         android.R.drawable.ic_menu_edit);
    118     return super.onCreateOptionsMenu(menu);
    119   }
    120 
    121   private String getAsString() {
    122     StringBuilder builder = new StringBuilder();
    123     for (String message : mLogcatMessages) {
    124       builder.append(message + "\n");
    125     }
    126     return builder.toString();
    127   }
    128 
    129   @Override
    130   public boolean onOptionsItemSelected(MenuItem item) {
    131     int itemId = item.getItemId();
    132     if (itemId == MenuId.JUMP_TO_BOTTOM.getId()) {
    133       getListView().setSelection(mLogcatMessages.size() - 1);
    134     } else if (itemId == MenuId.PREFERENCES.getId()) {
    135       startActivity(new Intent(this, Preferences.class));
    136     } else if (itemId == MenuId.SHARE.getId()) {
    137       Intent intent = new Intent(Intent.ACTION_SEND);
    138       intent.putExtra(Intent.EXTRA_TEXT, getAsString().toString());
    139       intent.putExtra(Intent.EXTRA_SUBJECT, "Logcat Dump");
    140       intent.setType("text/plain");
    141       startActivity(Intent.createChooser(intent, "Send Logcat to:"));
    142     } else if (itemId == MenuId.COPY.getId()) {
    143       ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    144       clipboard.setText(getAsString());
    145       Toast.makeText(this, "Copied to clipboard", Toast.LENGTH_SHORT).show();
    146     }
    147     return super.onOptionsItemSelected(item);
    148   }
    149 
    150   @Override
    151   protected void onStart() {
    152     mLogcatMessages.clear();
    153     Thread logcatWatcher = new Thread(new LogcatWatcher());
    154     logcatWatcher.setPriority(Thread.NORM_PRIORITY - 1);
    155     logcatWatcher.start();
    156     mAdapter.notifyDataSetInvalidated();
    157     super.onStart();
    158   }
    159 
    160   @Override
    161   protected void onPause() {
    162     super.onPause();
    163     mLogcatProcess.kill();
    164   }
    165 
    166   private class LogcatViewerAdapter extends BaseAdapter {
    167 
    168     @Override
    169     public int getCount() {
    170       return mLogcatMessages.size();
    171     }
    172 
    173     @Override
    174     public Object getItem(int position) {
    175       return mLogcatMessages.get(position);
    176     }
    177 
    178     @Override
    179     public long getItemId(int position) {
    180       return position;
    181     }
    182 
    183     @Override
    184     public View getView(final int position, View convertView, ViewGroup parent) {
    185       TextView view = new TextView(LogcatViewer.this);
    186       view.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
    187       view.setText(mLogcatMessages.get(position));
    188       return view;
    189     }
    190   }
    191 }
    192