| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package io.v.android.debug; |
| |
| import android.app.ListFragment; |
| import android.os.Bundle; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AbsListView; |
| import android.widget.ArrayAdapter; |
| import android.widget.ListAdapter; |
| import android.widget.ListView; |
| |
| import com.google.common.base.Throwables; |
| |
| import org.joda.time.Duration; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.concurrent.TimeUnit; |
| |
| import io.v.v23.android.R; |
| import rx.Observable; |
| import rx.Subscription; |
| import rx.android.schedulers.AndroidSchedulers; |
| import rx.schedulers.Schedulers; |
| |
| public class LogCatFragment extends ListFragment { |
| private static final String TAG = LogCatFragment.class.getSimpleName(); |
| private static final int |
| INITIAL_BUFFER = 1024, |
| MAX_SMOOTH_SCROLL = 64; |
| private static final Duration LOGCAT_UPDATE_PERIOD = Duration.millis(100); |
| |
| // http://stackoverflow.com/questions/12692103/read-logcat-programmatically-within-application |
| private static final Observable<String> RX_LOG_CAT = Observable.<String>create(subscriber -> { |
| try { |
| final Process process = Runtime.getRuntime().exec("logcat"); |
| try (final BufferedReader bufferedReader = |
| new BufferedReader(new InputStreamReader(process.getInputStream()))) { |
| String line; |
| while (!subscriber.isUnsubscribed() && (line = bufferedReader.readLine()) != null) { |
| subscriber.onNext(line); |
| } |
| } |
| subscriber.onCompleted(); |
| } catch (final IOException e) { |
| subscriber.onError(e); |
| } |
| }); |
| |
| private Subscription mLogCatSubscription; |
| private boolean mFollow = true; |
| private final AbsListView.OnScrollListener mScrollListener = |
| new AbsListView.OnScrollListener() { |
| private int mPrevious; |
| @Override |
| public void onScrollStateChanged(final AbsListView view, final int scrollState) { |
| } |
| |
| @Override |
| public void onScroll(final AbsListView view, final int firstVisibleItem, |
| final int visibleItemCount, final int totalItemCount) { |
| final boolean atBottom = visibleItemCount == 0 || |
| firstVisibleItem + visibleItemCount == totalItemCount; |
| if (firstVisibleItem < mPrevious) { |
| mFollow = false; |
| } else if (atBottom) { |
| mFollow = true; |
| } |
| mPrevious = firstVisibleItem; |
| } |
| }; |
| |
| private ListAdapter createAdapter() { |
| final ArrayList<String> buffer = new ArrayList<>(INITIAL_BUFFER); |
| final ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), |
| android.R.layout.simple_list_item_1, android.R.id.text1, buffer); |
| mLogCatSubscription = RX_LOG_CAT |
| .buffer(LOGCAT_UPDATE_PERIOD.getMillis(), TimeUnit.MILLISECONDS) |
| .filter(e -> !e.isEmpty()) |
| .subscribeOn(Schedulers.io()) |
| .onBackpressureBuffer() |
| .observeOn(AndroidSchedulers.mainThread()) |
| .subscribe( |
| s -> { |
| buffer.addAll(s); |
| adapter.notifyDataSetChanged(); |
| if (mFollow) { |
| final ListView listView = getListView(); |
| if (buffer.size() - listView.getLastVisiblePosition() > |
| MAX_SMOOTH_SCROLL) { |
| listView.setSelection(buffer.size() - 1); |
| } else { |
| listView.smoothScrollToPosition(buffer.size() - 1); |
| } |
| } |
| }, |
| t -> { |
| buffer.add(Throwables.getStackTraceAsString(t)); |
| Log.e(TAG, "Error while following logs", t); |
| }); |
| return adapter; |
| } |
| |
| @Override |
| public void onCreate(final Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setListAdapter(createAdapter()); |
| } |
| |
| @Override |
| public void onDestroy() { |
| mLogCatSubscription.unsubscribe(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public View onCreateView(final LayoutInflater inflater, final ViewGroup container, |
| final Bundle savedInstanceState) { |
| return inflater.inflate(R.layout.logcat, container, false); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mFollow) { |
| setSelection(getListAdapter().getCount() - 1); |
| } |
| getListView().setOnScrollListener(mScrollListener); |
| } |
| } |