package jp.co.cyberagent.stf;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.IBinder;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import jp.co.cyberagent.stf.io.MessageReader;
import jp.co.cyberagent.stf.io.MessageRouter;
import jp.co.cyberagent.stf.io.MessageWriter;
import jp.co.cyberagent.stf.monitor.AbstractMonitor;
import jp.co.cyberagent.stf.monitor.AirplaneModeMonitor;
import jp.co.cyberagent.stf.monitor.BatteryMonitor;
import jp.co.cyberagent.stf.monitor.BrowserPackageMonitor;
import jp.co.cyberagent.stf.monitor.ConnectivityMonitor;
import jp.co.cyberagent.stf.monitor.PhoneStateMonitor;
import jp.co.cyberagent.stf.monitor.RotationMonitor;
import jp.co.cyberagent.stf.proto.Wire;
import jp.co.cyberagent.stf.query.DoAddAccountMenuResponder;
import jp.co.cyberagent.stf.query.DoIdentifyResponder;
import jp.co.cyberagent.stf.query.DoRemoveAccountResponder;
import jp.co.cyberagent.stf.query.GetAccountsResponder;
import jp.co.cyberagent.stf.query.GetBrowsersResponder;
import jp.co.cyberagent.stf.query.GetClipboardResponder;
import jp.co.cyberagent.stf.query.GetDisplayResponder;
import jp.co.cyberagent.stf.query.GetPropertiesResponder;
import jp.co.cyberagent.stf.query.GetRingerModeResponder;
import jp.co.cyberagent.stf.query.GetSdStatusResponder;
import jp.co.cyberagent.stf.query.GetVersionResponder;
import jp.co.cyberagent.stf.query.GetWifiStatusResponder;
import jp.co.cyberagent.stf.query.SetClipboardResponder;
import jp.co.cyberagent.stf.query.SetKeyguardStateResponder;
import jp.co.cyberagent.stf.query.SetMasterMuteResponder;
import jp.co.cyberagent.stf.query.SetRingerModeResponder;
import jp.co.cyberagent.stf.query.SetWakeLockResponder;
import jp.co.cyberagent.stf.query.SetWifiEnabledResponder;
public class Service extends android.app.Service {
public static final String ACTION_START = "jp.co.cyberagent.stf.ACTION_START";
public static final String ACTION_STOP = "jp.co.cyberagent.stf.ACTION_STOP";
public static final String EXTRA_SOCKET = "jp.co.cyberagent.stf.EXTRA_SOCKET";
public static final String DEFAULT_SOCKET = "stfservice";
private static final String TAG = "STFService";
private static final int NOTIFICATION_ID = 0x1;
private List<AbstractMonitor> monitors = new ArrayList<AbstractMonitor>();
private ExecutorService executor = Executors.newCachedThreadPool();
private LocalServerSocket acceptor;
private boolean started = false;
private MessageWriter.Pool writers = new MessageWriter.Pool();
// We can only access CLIPBOARD_SERVICE from the main thread
private static Object clipboardManager;
public static Object getClipboardManager() {
return clipboardManager;
}
@Override
public IBinder onBind(Intent intent) {
// We don't support binding to this service
return null;
}
@Override
public void onCreate() {
super.onCreate();
clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE);
Intent notificationIntent = new Intent(this, IdentityActivity.class);
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_menu_info_details)
.setTicker(getString(R.string.service_ticker))
.setContentTitle(getString(R.string.service_title))
.setContentText(getString(R.string.service_text))
.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0))
.setWhen(System.currentTimeMillis())
.build();
startForeground(NOTIFICATION_ID, notification);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Stopping service");
stopForeground(true);
if (acceptor != null) {
try {
acceptor.close();
}
catch (IOException e) {
// We don't care
}
}
try {
executor.shutdownNow();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
// Too bad
}
finally {
started = false;
// Unfortunately, we have no way to clean up some Binder-based callbacks
// (namely IRotationWatcher) on lower API levels without killing the process,
// which will allow DeathRecipient to handle it on their side.
Process.killProcess(Process.myPid());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
String action = intent.getAction();
if (ACTION_START.equals(action)) {
if (!started) {
Log.i(TAG, "Starting service");
String socketName = intent.getStringExtra(EXTRA_SOCKET);
if (socketName == null) {
socketName = DEFAULT_SOCKET;
}
try {
acceptor = new LocalServerSocket(socketName);
addMonitor(new BatteryMonitor(this, writers));
addMonitor(new ConnectivityMonitor(this, writers));
addMonitor(new PhoneStateMonitor(this, writers));
addMonitor(new RotationMonitor(this, writers));
addMonitor(new AirplaneModeMonitor(this, writers));
addMonitor(new BrowserPackageMonitor(this, writers));
executor.submit(new Server(acceptor));
started = true;
}
catch (UnknownHostException e) {
Log.e(TAG, e.getMessage());
}
catch (IOException e) {
e.printStackTrace();
}
}
else {
Log.w(TAG, "Service is already running");
}
}
else if (ACTION_STOP.equals(action)) {
stopSelf();
}
else {
Log.e(TAG, "Unknown action " + action);
}
return START_NOT_STICKY;
}
@Override
public void onLowMemory() {
Log.w(TAG, "Low memory");
}
private void addMonitor(AbstractMonitor monitor) {
monitors.add(monitor);
executor.submit(monitor);
}
class Server extends Thread {
private LocalServerSocket acceptor;
private ExecutorService executor = Executors.newCachedThreadPool();
public Server(LocalServerSocket acceptor) {
this.acceptor = acceptor;
}
@Override
public void interrupt() {
super.interrupt();
try {
acceptor.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String socketName = acceptor.getLocalSocketAddress().getName();
Log.i(TAG, String.format("Server listening on @%s", socketName));
try {
while (!isInterrupted()) {
Connection conn = new Connection(acceptor.accept());
executor.submit(conn);
}
}
catch (IOException e) {
}
finally {
Log.i(TAG, "Server stopping");
try {
acceptor.close();
}
catch (IOException e) {
}
stopSelf();
}
}
class Connection extends Thread {
private LocalSocket socket;
public Connection(LocalSocket socket) {
this.socket = socket;
}
@Override
public void interrupt() {
super.interrupt();
try {
socket.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
Log.i(TAG, "Connection started");
MessageWriter writer = null;
MessageRouter router = null;
try {
MessageReader reader = new MessageReader(socket.getInputStream());
writer = new MessageWriter(socket.getOutputStream());
writers.add(writer);
router = new MessageRouter(writer);
router.register(Wire.MessageType.DO_IDENTIFY,
new DoIdentifyResponder(getBaseContext()));
router.register(Wire.MessageType.DO_ADD_ACCOUNT_MENU,
new DoAddAccountMenuResponder(getBaseContext()));
router.register(Wire.MessageType.DO_REMOVE_ACCOUNT,
new DoRemoveAccountResponder(getBaseContext()));
router.register(Wire.MessageType.GET_ACCOUNTS,
new GetAccountsResponder(getBaseContext()));
router.register(Wire.MessageType.GET_BROWSERS,
new GetBrowsersResponder(getBaseContext()));
router.register(Wire.MessageType.GET_CLIPBOARD,
new GetClipboardResponder(getBaseContext()));
router.register(Wire.MessageType.GET_DISPLAY,
new GetDisplayResponder(getBaseContext()));
router.register(Wire.MessageType.GET_PROPERTIES,
new GetPropertiesResponder(getBaseContext()));
router.register(Wire.MessageType.GET_RINGER_MODE,
new GetRingerModeResponder(getBaseContext()));
router.register(Wire.MessageType.GET_SD_STATUS,
new GetSdStatusResponder(getBaseContext()));
router.register(Wire.MessageType.GET_VERSION,
new GetVersionResponder(getBaseContext()));
router.register(Wire.MessageType.GET_WIFI_STATUS,
new GetWifiStatusResponder(getBaseContext()));
router.register(Wire.MessageType.SET_CLIPBOARD,
new SetClipboardResponder(getBaseContext()));
router.register(Wire.MessageType.SET_KEYGUARD_STATE,
new SetKeyguardStateResponder(getBaseContext()));
router.register(Wire.MessageType.SET_RINGER_MODE,
new SetRingerModeResponder(getBaseContext()));
router.register(Wire.MessageType.SET_WAKE_LOCK,
new SetWakeLockResponder(getBaseContext()));
router.register(Wire.MessageType.SET_WIFI_ENABLED,
new SetWifiEnabledResponder(getBaseContext()));
router.register(Wire.MessageType.SET_MASTER_MUTE,
new SetMasterMuteResponder(getBaseContext()));
for (AbstractMonitor monitor : monitors) {
monitor.peek(writer);
}
while (!isInterrupted()) {
Wire.Envelope envelope = reader.read();
if (envelope == null) {
break;
}
router.route(envelope);
}
}
catch (InvalidProtocolBufferException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
catch (IOException e) {
}
catch (Exception e) {
e.printStackTrace();
}
finally {
Log.i(TAG, "Connection stopping");
writers.remove(writer);
if (router != null) {
router.cleanup();
}
try {
socket.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}