1 /* 2 * Copyright (C) 2017 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.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; getId()54 public int getId() { 55 return ordinal() + Menu.FIRST; 56 } 57 } 58 59 private class LogcatWatcher implements Runnable { 60 @Override run()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 onCreate(Bundle savedInstanceState)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 onCreateOptionsMenu(Menu menu)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 getAsString()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 onOptionsItemSelected(MenuItem item)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 onStart()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 onPause()161 protected void onPause() { 162 super.onPause(); 163 mLogcatProcess.kill(); 164 } 165 166 private class LogcatViewerAdapter extends BaseAdapter { 167 168 @Override getCount()169 public int getCount() { 170 return mLogcatMessages.size(); 171 } 172 173 @Override getItem(int position)174 public Object getItem(int position) { 175 return mLogcatMessages.get(position); 176 } 177 178 @Override getItemId(int position)179 public long getItemId(int position) { 180 return position; 181 } 182 183 @Override getView(final int position, View convertView, ViewGroup parent)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