/*****************************************************************************
* 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 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;
import android.text.format.DateFormat;
import org.videolan.vlc.R;
import org.videolan.vlc.util.AndroidDevices;
import org.videolan.vlc.util.Logcat;
import org.videolan.vlc.util.Util;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
import java.util.List;
public class DebugLogService extends Service implements Logcat.Callback, Runnable {
private static final int MSG_STARTED = 0;
private static final int MSG_STOPPED = 1;
private static final int MSG_ONLOG = 2;
private static final int MSG_SAVED = 3;
private static final int MAX_LINES = 20000;
private Logcat mLogcat = null;
private LinkedList<String> mLogList = new LinkedList<String>();
private Thread mSaveThread = null;
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 save() {
mService.save();
}
public void registerCallback(IDebugLogServiceCallback cb) {
mService.registerCallback(cb);
}
public void unregisterCallback(IDebugLogServiceCallback cb) {
mService.unregisterCallback(cb);
}
}
private synchronized void sendMessage(int what, String str) {
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: {
cb.onStarted(mLogList);
break;
}
case MSG_ONLOG:
cb.onLog(str);
break;
case MSG_SAVED:
cb.onSaved(str != null ? true : false, str);
break;
}
} catch (RemoteException e) {
}
}
mCallbacks.finishBroadcast();
}
@Override
public synchronized void onLog(String log) {
if (mLogList.size() > MAX_LINES)
mLogList.remove(0);
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();
}
/* mSaveThread */
@Override
public void run() {
final CharSequence timestamp = DateFormat.format(
"yyyyMMdd_kkmmss", System.currentTimeMillis());
final String filename = AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY + "/vlc_logcat_" + timestamp + ".log";
boolean saved = true;
FileOutputStream fos = null;
OutputStreamWriter output = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(filename);
output = new OutputStreamWriter(fos);
bw = new BufferedWriter(output);
synchronized (this) {
for (String line : mLogList) {
bw.write(line);
bw.newLine();
}
}
} catch (FileNotFoundException e) {
saved = false;
} catch (IOException ioe) {
saved = false;
} finally {
saved &= Util.close(bw);
saved &= Util.close(output);
saved &= Util.close(fos);
}
synchronized (this) {
mSaveThread = null;
sendMessage(MSG_SAVED, saved ? filename : null);
}
}
public synchronized void save() {
if (mSaveThread != null) {
try {
mSaveThread.join();
} catch (InterruptedException e) {}
mSaveThread = null;
}
mSaveThread = new Thread(this);
mSaveThread.start();
}
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 {
void onStarted(List<String> lostList);
void onStopped();
void onLog(String msg);
void onSaved(boolean success, String path);
}
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 List<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);
}
});
}
@Override
public void onSaved(final boolean success, final String path) throws RemoteException {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onSaved(success, path);
}
});
}
};
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (Client.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 boolean save() {
synchronized (this) {
if (mIDebugLogService != null) {
try {
mIDebugLogService.save();
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);
}
}
}