/* * Copyright (C) 2017 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.googlecode.android_scripting.activity; import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.ClipboardManager; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import android.widget.Toast; import com.googlecode.android_scripting.ActivityFlinger; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.Process; import com.googlecode.android_scripting.R; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.List; public class LogcatViewer extends ListActivity { private List mLogcatMessages; private int mOldLastPosition; private LogcatViewerAdapter mAdapter; private Process mLogcatProcess; private static enum MenuId { HELP, PREFERENCES, JUMP_TO_BOTTOM, SHARE, COPY; public int getId() { return ordinal() + Menu.FIRST; } } private class LogcatWatcher implements Runnable { @Override public void run() { mLogcatProcess = new Process(); mLogcatProcess.setBinary(new File("/system/bin/logcat")); mLogcatProcess.start(null); try { BufferedReader br = new BufferedReader(new InputStreamReader(mLogcatProcess.getIn())); while (true) { final String line = br.readLine(); if (line == null) { break; } runOnUiThread(new Runnable() { @Override public void run() { mLogcatMessages.add(line); mAdapter.notifyDataSetInvalidated(); // This logic performs what transcriptMode="normal" should do. Since that doesn't seem // to work, we do it this way. int lastVisiblePosition = getListView().getLastVisiblePosition(); int lastPosition = mLogcatMessages.size() - 1; if (lastVisiblePosition == mOldLastPosition || lastVisiblePosition == -1) { getListView().setSelection(lastPosition); } mOldLastPosition = lastPosition; } }); } } catch (IOException e) { Log.e("Failed to read from logcat process.", e); } finally { mLogcatProcess.kill(); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); CustomizeWindow.requestCustomTitle(this, "Logcat", R.layout.logcat_viewer); mLogcatMessages = new LinkedList(); mOldLastPosition = 0; mAdapter = new LogcatViewerAdapter(); setListAdapter(mAdapter); ActivityFlinger.attachView(getListView(), this); ActivityFlinger.attachView(getWindow().getDecorView(), this); } @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, MenuId.PREFERENCES.getId(), Menu.NONE, "Preferences").setIcon( android.R.drawable.ic_menu_preferences); menu.add(Menu.NONE, MenuId.JUMP_TO_BOTTOM.getId(), Menu.NONE, "Jump to Bottom").setIcon( android.R.drawable.ic_menu_revert); menu.add(Menu.NONE, MenuId.SHARE.getId(), Menu.NONE, "Share").setIcon( android.R.drawable.ic_menu_share); menu.add(Menu.NONE, MenuId.COPY.getId(), Menu.NONE, "Copy").setIcon( android.R.drawable.ic_menu_edit); return super.onCreateOptionsMenu(menu); } private String getAsString() { StringBuilder builder = new StringBuilder(); for (String message : mLogcatMessages) { builder.append(message + "\n"); } return builder.toString(); } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == MenuId.JUMP_TO_BOTTOM.getId()) { getListView().setSelection(mLogcatMessages.size() - 1); } else if (itemId == MenuId.PREFERENCES.getId()) { startActivity(new Intent(this, Preferences.class)); } else if (itemId == MenuId.SHARE.getId()) { Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, getAsString().toString()); intent.putExtra(Intent.EXTRA_SUBJECT, "Logcat Dump"); intent.setType("text/plain"); startActivity(Intent.createChooser(intent, "Send Logcat to:")); } else if (itemId == MenuId.COPY.getId()) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); clipboard.setText(getAsString()); Toast.makeText(this, "Copied to clipboard", Toast.LENGTH_SHORT).show(); } return super.onOptionsItemSelected(item); } @Override protected void onStart() { mLogcatMessages.clear(); Thread logcatWatcher = new Thread(new LogcatWatcher()); logcatWatcher.setPriority(Thread.NORM_PRIORITY - 1); logcatWatcher.start(); mAdapter.notifyDataSetInvalidated(); super.onStart(); } @Override protected void onPause() { super.onPause(); mLogcatProcess.kill(); } private class LogcatViewerAdapter extends BaseAdapter { @Override public int getCount() { return mLogcatMessages.size(); } @Override public Object getItem(int position) { return mLogcatMessages.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { TextView view = new TextView(LogcatViewer.this); view.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); view.setText(mLogcatMessages.get(position)); return view; } } }