[Android] [PATCH 2/3] DebugLog: use a service with a thread
Thomas Guillem
thomas at gllm.fr
Mon Feb 9 19:31:56 CET 2015
- Remove dependency to LibVLC log stuff.
- Use a thread (via Logcat class) in a service: that way, logs are not lost and
are always updated.
- Use a ListView to show the logs: easily scrollable.
- There is a notification to notify that logs are being recorded, and to access
the logs.
---
vlc-android/AndroidManifest.xml | 6 +-
vlc-android/res/layout/debug_log.xml | 11 +-
vlc-android/res/layout/debug_log_item.xml | 5 +
vlc-android/res/values/strings.xml | 2 +
.../src/org/videolan/vlc/gui/DebugLogActivity.java | 161 +++++++----
.../src/org/videolan/vlc/gui/DebugLogService.java | 297 +++++++++++++++++++++
.../src/org/videolan/vlc/gui/IDebugLogService.aidl | 32 +++
.../videolan/vlc/gui/IDebugLogServiceCallback.aidl | 28 ++
8 files changed, 483 insertions(+), 59 deletions(-)
create mode 100644 vlc-android/res/layout/debug_log_item.xml
create mode 100644 vlc-android/src/org/videolan/vlc/gui/DebugLogService.java
create mode 100644 vlc-android/src/org/videolan/vlc/gui/IDebugLogService.aidl
create mode 100644 vlc-android/src/org/videolan/vlc/gui/IDebugLogServiceCallback.aidl
diff --git a/vlc-android/AndroidManifest.xml b/vlc-android/AndroidManifest.xml
index bc5de67..59b5241 100644
--- a/vlc-android/AndroidManifest.xml
+++ b/vlc-android/AndroidManifest.xml
@@ -61,7 +61,11 @@
android:name=".gui.BrowserActivity"
android:label="@string/mediafiles"
android:theme="@style/Theme.VLC.NoTitleBar" />
- <activity android:name=".gui.DebugLogActivity" />
+
+ <activity android:name=".gui.DebugLogActivity"
+ android:launchMode="singleTop" />
+ <service android:name=".gui.DebugLogService" />
+
<activity
android:name=".gui.NativeCrashActivity"
android:process=":NativeCrashActivity"
diff --git a/vlc-android/res/layout/debug_log.xml b/vlc-android/res/layout/debug_log.xml
index eaf27a7..071d0d9 100644
--- a/vlc-android/res/layout/debug_log.xml
+++ b/vlc-android/res/layout/debug_log.xml
@@ -36,11 +36,10 @@
android:enabled="false"
android:text="@string/copy_to_clipboard" />
- <TextView
- android:id="@+id/textview"
+ <ListView
+ android:id="@+id/log_list"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="vertical"
- android:textIsSelectable="true" />
+ android:layout_height="wrap_content"
+ android:paddingTop="10dip" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/vlc-android/res/layout/debug_log_item.xml b/vlc-android/res/layout/debug_log_item.xml
new file mode 100644
index 0000000..e6e74c9
--- /dev/null
+++ b/vlc-android/res/layout/debug_log_item.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:typeface="monospace" />
diff --git a/vlc-android/res/values/strings.xml b/vlc-android/res/values/strings.xml
index 666b1b6..f485878 100644
--- a/vlc-android/res/values/strings.xml
+++ b/vlc-android/res/values/strings.xml
@@ -291,6 +291,8 @@
<string name="start_logging">Start logging</string>
<string name="stop_logging">Stop logging</string>
<string name="clear_log">Clear log</string>
+ <string name="log_service_title">VLC\'s logs are being recorded</string>
+ <string name="log_service_text">Click to view the logs</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="copied_to_clipboard">Copied log to clipboard.</string>
<string name="quit">Quit and restart application</string>
diff --git a/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.java b/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.java
index c3c1586..33fc037 100644
--- a/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.java
@@ -1,7 +1,7 @@
/*****************************************************************************
* DebugLogActivity.java
*****************************************************************************
- * Copyright © 2013 VLC authors and VideoLAN
+ * Copyright © 2013-2015 VLC authors and VideoLAN
* Copyright © 2013 Edward Wang
*
* This program is free software; you can redistribute it and/or modify
@@ -20,85 +20,142 @@
*****************************************************************************/
package org.videolan.vlc.gui;
+import java.util.ArrayList;
+import java.util.Arrays;
+
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.LibVlcException;
import org.videolan.vlc.R;
+import org.videolan.vlc.util.Logcat;
import org.videolan.vlc.util.VLCInstance;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-public class DebugLogActivity extends Activity {
+public class DebugLogActivity extends Activity implements DebugLogService.Client.Callback {
public final static String TAG = "VLC/DebugLogActivity";
+ private DebugLogService.Client mClient = null;
+ private Button mStartButton = null;
+ private Button mStopButton = null;
+ private Button mCopyButton = null;
+ private Button mClearButton = null;
+ private ListView mLogView;
+ private ArrayList<String> mLogList = null;
+ private ArrayAdapter<String> mLogAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug_log);
- final LibVLC instance;
- try {
- instance = VLCInstance.getLibVlcInstance();
- } catch (LibVlcException e) { return; }
+ mStartButton = (Button)findViewById(R.id.start_log);
+ mStopButton = (Button)findViewById(R.id.stop_log);
+ mLogView = (ListView) findViewById(R.id.log_list);
+ mCopyButton = (Button)findViewById(R.id.copy_to_clipboard);
+ mClearButton = (Button)findViewById(R.id.clear_log);
- final Button startLog = (Button)findViewById(R.id.start_log);
- final Button stopLog = (Button)findViewById(R.id.stop_log);
+ mClient = new DebugLogService.Client(this, this);
- startLog.setEnabled(! instance.isDebugBuffering());
- stopLog.setEnabled(instance.isDebugBuffering());
+ mStartButton.setEnabled(false);
+ mStopButton.setEnabled(false);
+ mCopyButton.setEnabled(false);
+ mClearButton.setEnabled(false);
- startLog.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- instance.startDebugBuffer();
- startLog.setEnabled(false);
- stopLog.setEnabled(true);
- }
- });
-
- stopLog.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- instance.stopDebugBuffer();
- stopLog.setEnabled(false);
- startLog.setEnabled(true);
- }
- });
-
- Button clearLog = (Button)findViewById(R.id.clear_log);
- clearLog.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- instance.clearBuffer();
- updateTextView(instance);
- }
- });
+ mStartButton.setOnClickListener(mStartClickListener);
+ mStopButton.setOnClickListener(mStopClickListener);
+ mClearButton.setOnClickListener(mClearClickListener);
- updateTextView(instance);
+ mCopyButton.setOnClickListener(mCopyClickListener);
+ }
- Button copyToClipboard = (Button)findViewById(R.id.copy_to_clipboard);
- copyToClipboard.setEnabled(((TextView)findViewById(R.id.textview)).getText().length() > 0);
- copyToClipboard.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- copyTextToClipboard();
- Toast.makeText(DebugLogActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
- }
- });
+ @Override
+ protected void onDestroy() {
+ mClient.release();
+ super.onDestroy();
}
+ private View.OnClickListener mStartClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mStartButton.setEnabled(false);
+ mStopButton.setEnabled(false);
+ mClient.start();
+ }
+ };
+
+ private View.OnClickListener mStopClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mStartButton.setEnabled(false);
+ mStopButton.setEnabled(false);
+ mClient.stop();
+ }
+ };
+
+ private View.OnClickListener mClearClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mClient.clear();
+ if (mLogList != null) {
+ mLogList.clear();
+ mLogAdapter.notifyDataSetChanged();
+ }
+ mClearButton.setEnabled(false);
+ mCopyButton.setEnabled(false);
+ }
+ };
+
@SuppressWarnings("deprecation")
- private void copyTextToClipboard() {
- android.text.ClipboardManager clipboard = (android.text.ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
- clipboard.setText(((TextView)findViewById(R.id.textview)).getText());
+ private View.OnClickListener mCopyClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final StringBuffer buffer = new StringBuffer();
+ for (String line : mLogList)
+ buffer.append(line+"\n");
+
+ android.text.ClipboardManager clipboard = (android.text.ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
+ clipboard.setText(buffer);
+
+ Toast.makeText(DebugLogActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
+ }
+ };
+
+ @Override
+ public void onStarted(String[] logList) {
+ mStartButton.setEnabled(false);
+ mStopButton.setEnabled(true);
+ if (logList.length > 0) {
+ mCopyButton.setEnabled(true);
+ mClearButton.setEnabled(true);
+ }
+ mLogList = new ArrayList<String>(Arrays.asList(logList));
+ mLogAdapter = new ArrayAdapter<String>(this, R.layout.debug_log_item, mLogList);
+ mLogView.setAdapter(mLogAdapter);
+ mLogView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
+ if (mLogList.size() > 0) {
+ mLogView.setSelection(mLogList.size() - 1);
+ }
}
- private void updateTextView(final LibVLC instance) {
- TextView textView = (TextView)findViewById(R.id.textview);
- textView.setText(instance.getBufferContent());
+ @Override
+ public void onStopped() {
+ mStartButton.setEnabled(true);
+ mStopButton.setEnabled(false);
+ }
+
+ @Override
+ public void onLog(String msg) {
+ if (mLogList != null) {
+ mLogList.add(msg);
+ mLogAdapter.notifyDataSetChanged();
+ mCopyButton.setEnabled(true);
+ mClearButton.setEnabled(true);
+ }
}
}
diff --git a/vlc-android/src/org/videolan/vlc/gui/DebugLogService.java b/vlc-android/src/org/videolan/vlc/gui/DebugLogService.java
new file mode 100644
index 0000000..bc9548e
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/DebugLogService.java
@@ -0,0 +1,297 @@
+/*****************************************************************************
+ * DebugLogService.java
+ *****************************************************************************
+ * Copyright © 2015 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+package org.videolan.vlc.gui;
+
+import java.util.ArrayList;
+
+import org.videolan.vlc.R;
+import org.videolan.vlc.util.Logcat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.support.v4.app.NotificationCompat;
+
+public class DebugLogService extends Service implements Logcat.Callback {
+
+ private static final int MSG_STARTED = 0;
+ private static final int MSG_STOPPED = 1;
+ private static final int MSG_ONLOG = 2;
+
+ private Logcat mLogcat = null;
+ private ArrayList<String> mLogList = new ArrayList<String>();
+ private final RemoteCallbackList<IDebugLogServiceCallback> mCallbacks = new RemoteCallbackList<IDebugLogServiceCallback>();
+ private final IBinder mBinder = new DebugLogServiceStub(this);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ static class DebugLogServiceStub extends IDebugLogService.Stub {
+ private DebugLogService mService;
+ DebugLogServiceStub(DebugLogService service) {
+ mService = service;
+ }
+ public void start() {
+ mService.start();
+ }
+ public void stop() {
+ mService.stop();
+ }
+ public void clear() {
+ mService.clear();
+ }
+ public void registerCallback(IDebugLogServiceCallback cb) {
+ mService.registerCallback(cb);
+ }
+ public void unregisterCallback(IDebugLogServiceCallback cb) {
+ mService.unregisterCallback(cb);
+ }
+ }
+
+ private synchronized void sendMessage(int what, String log) {
+ int i = mCallbacks.beginBroadcast();
+ while (i > 0) {
+ i--;
+ final IDebugLogServiceCallback cb = mCallbacks.getBroadcastItem(i);
+ try {
+ switch (what) {
+ case MSG_STOPPED:
+ cb.onStopped();
+ break;
+ case MSG_STARTED: {
+ String[] logs = mLogList.toArray(new String[mLogList.size()]);
+ cb.onStarted(logs);
+ break;
+ } case MSG_ONLOG:
+ cb.onLog(log);
+ break;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ @Override
+ public synchronized void onLog(String log) {
+ mLogList.add(log);
+ sendMessage(MSG_ONLOG, log);
+ }
+
+ public synchronized void start() {
+ if (mLogcat != null)
+ return;
+ clear();
+ mLogcat = new Logcat();
+ mLogcat.start(this);
+
+ final Intent debugLogIntent = new Intent(this, DebugLogActivity.class);
+ debugLogIntent.setAction("android.intent.action.MAIN");
+ debugLogIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ final PendingIntent pi = PendingIntent.getActivity(this, 0, debugLogIntent, 0);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ builder.setContentTitle(getResources().getString(R.string.log_service_title));
+ builder.setContentText(getResources().getString(R.string.log_service_text));
+ builder.setSmallIcon(R.drawable.ic_stat_vlc);
+ builder.setContentIntent(pi);
+ final Notification notification = builder.build();
+ startForeground(R.string.log_service_title, notification);
+
+ startService(new Intent(this, DebugLogService.class));
+ sendMessage(MSG_STARTED, null);
+ }
+
+ public synchronized void stop() {
+ mLogcat.stop();
+ mLogcat = null;
+ sendMessage(MSG_STOPPED, null);
+ stopForeground(true);
+ stopSelf();
+ }
+
+ public synchronized void clear() {
+ mLogList.clear();
+ }
+
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_STICKY;
+ }
+
+ private void registerCallback(IDebugLogServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.register(cb);
+ sendMessage(mLogcat != null ? MSG_STARTED : MSG_STOPPED, null);
+ }
+ }
+
+ private void unregisterCallback(IDebugLogServiceCallback cb) {
+ if (cb != null)
+ mCallbacks.unregister(cb);
+ }
+
+ public static class Client {
+
+ public interface Callback {
+ public void onStarted(String[] lostList);
+ public void onStopped();
+ public void onLog(String msg);
+ }
+
+ private boolean mBound = false;
+ private final Context mContext;
+ private Callback mCallback;
+ private IDebugLogService mIDebugLogService;
+ private Handler mHandler;
+
+ private final IDebugLogServiceCallback.Stub mICallback = new IDebugLogServiceCallback.Stub() {
+ @Override
+ public void onStopped() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onStopped();
+ }
+ });
+ }
+
+ @Override
+ public void onStarted(final String[] logList) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onStarted(logList);
+ }
+ });
+ }
+
+ @Override
+ public void onLog(final String msg) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onLog(msg);
+ }
+ });
+ }
+ };
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mIDebugLogService = IDebugLogService.Stub.asInterface(service);
+ try {
+ mIDebugLogService.registerCallback(mICallback);
+ } catch (RemoteException e) {
+ release();
+ mContext.stopService(new Intent(mContext, DebugLogService.class));
+ mCallback.onStopped();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ release();
+ mContext.stopService(new Intent(mContext, DebugLogService.class));
+ mCallback.onStopped();
+ }
+ };
+
+ public Client(Context context, Callback cb) throws IllegalArgumentException {
+ if (context == null | cb == null)
+ throw new IllegalArgumentException("Context and Callback can't be null");
+
+ mContext = context;
+ mCallback = cb;
+ mHandler = new Handler(Looper.getMainLooper());
+ mBound = mContext.bindService(new Intent(mContext, DebugLogService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ public boolean start() {
+ synchronized (this) {
+ if (mIDebugLogService != null) {
+ try {
+ mIDebugLogService.start();
+ return true;
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+ }
+
+ public boolean stop() {
+ synchronized (this) {
+ if (mIDebugLogService != null) {
+ try {
+ mIDebugLogService.stop();
+ return true;
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+ }
+
+ public boolean clear() {
+ synchronized (this) {
+ if (mIDebugLogService != null) {
+ try {
+ mIDebugLogService.clear();
+ return true;
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+ }
+
+ public void release() {
+ if (mBound) {
+ synchronized (this) {
+ if (mIDebugLogService != null && mICallback != null) {
+ try {
+ mIDebugLogService.unregisterCallback(mICallback);
+ } catch (RemoteException e) {
+ }
+ mIDebugLogService = null;
+ }
+ }
+ mBound = false;
+ mContext.unbindService(mServiceConnection);
+ }
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/vlc-android/src/org/videolan/vlc/gui/IDebugLogService.aidl b/vlc-android/src/org/videolan/vlc/gui/IDebugLogService.aidl
new file mode 100644
index 0000000..6bd34d9
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/IDebugLogService.aidl
@@ -0,0 +1,32 @@
+/*****************************************************************************
+ * IDebugLogService.aidl
+ *****************************************************************************
+ * Copyright © 2015 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+package org.videolan.vlc.gui;
+import org.videolan.vlc.gui.IDebugLogServiceCallback;
+
+interface IDebugLogService
+{
+ void start();
+ void stop();
+ void clear();
+ void registerCallback(IDebugLogServiceCallback cb);
+ void unregisterCallback(IDebugLogServiceCallback cb);
+}
+
diff --git a/vlc-android/src/org/videolan/vlc/gui/IDebugLogServiceCallback.aidl b/vlc-android/src/org/videolan/vlc/gui/IDebugLogServiceCallback.aidl
new file mode 100644
index 0000000..60ad9df
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/IDebugLogServiceCallback.aidl
@@ -0,0 +1,28 @@
+/*****************************************************************************
+ * IDebugLogServiceCallback.aidl
+ *****************************************************************************
+ * Copyright © 2015 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+package org.videolan.vlc.gui;
+
+interface IDebugLogServiceCallback
+{
+ void onStarted(out String[] logList);
+ void onStopped();
+ void onLog(String msg);
+}
--
2.1.3
More information about the Android
mailing list