19f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/*
2f04335f899f2cce69f843692a3cb9cec229683c2tturney * Copyright (C) 2017 The Android Open Source Project
39f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
4f04335f899f2cce69f843692a3cb9cec229683c2tturney * Licensed under the Apache License, Version 2.0 (the "License");
5f04335f899f2cce69f843692a3cb9cec229683c2tturney * you may not use this file except in compliance with the License.
6f04335f899f2cce69f843692a3cb9cec229683c2tturney * You may obtain a copy of the License at
79f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
8f04335f899f2cce69f843692a3cb9cec229683c2tturney *      http://www.apache.org/licenses/LICENSE-2.0
99f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Unless required by applicable law or agreed to in writing, software
11f04335f899f2cce69f843692a3cb9cec229683c2tturney * distributed under the License is distributed on an "AS IS" BASIS,
12f04335f899f2cce69f843692a3cb9cec229683c2tturney * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f04335f899f2cce69f843692a3cb9cec229683c2tturney * See the License for the specific language governing permissions and
14f04335f899f2cce69f843692a3cb9cec229683c2tturney * limitations under the License.
159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */
169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipackage com.googlecode.android_scripting.activity;
189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.app.Notification;
209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.app.NotificationManager;
219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.app.PendingIntent;
229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.Context;
239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.Intent;
249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.SharedPreferences;
259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.os.Binder;
269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.os.IBinder;
279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.os.StrictMode;
289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.preference.PreferenceManager;
299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.AndroidProxy;
319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.BaseApplication;
329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.Constants;
339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.ForegroundService;
349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.NotificationIdFactory;
359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.R;
369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.ScriptLauncher;
379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.ScriptProcess;
389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.interpreter.InterpreterConfiguration;
399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.interpreter.InterpreterProcess;
409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.interpreter.shell.ShellInterpreter;
419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.connectbot.ConsoleActivity;
439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.connectbot.service.TerminalManager;
449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.File;
469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.ref.WeakReference;
479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.net.InetSocketAddress;
489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.ArrayList;
499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.List;
509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.Map;
519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.concurrent.ConcurrentHashMap;
529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/**
549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * A service that allows scripts and the RPC server to run in the background.
559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */
579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipublic class ScriptingLayerService extends ForegroundService {
589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static final int NOTIFICATION_ID = NotificationIdFactory.create();
599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private final IBinder mBinder;
619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private final Map<Integer, InterpreterProcess> mProcessMap;
629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private final String LOG_TAG = "sl4a";
639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private volatile int mModCount = 0;
649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private NotificationManager mNotificationManager;
659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private Notification mNotification;
669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private PendingIntent mNotificationPendingIntent;
679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private InterpreterConfiguration mInterpreterConfiguration;
689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private volatile WeakReference<InterpreterProcess> mRecentlyKilledProcess;
709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private TerminalManager mTerminalManager;
729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private SharedPreferences mPreferences = null;
749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private boolean mHide;
759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public class LocalBinder extends Binder {
779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    public ScriptingLayerService getService() {
789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return ScriptingLayerService.this;
799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @Override
839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public IBinder onBind(Intent intent) {
849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mBinder;
859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public ScriptingLayerService() {
889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    super(NOTIFICATION_ID);
899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mProcessMap = new ConcurrentHashMap<Integer, InterpreterProcess>();
909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mBinder = new LocalBinder();
919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @Override
949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public void onCreate() {
959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    super.onCreate();
969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mInterpreterConfiguration = ((BaseApplication) getApplication()).getInterpreterConfiguration();
979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mRecentlyKilledProcess = new WeakReference<InterpreterProcess>(null);
999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mTerminalManager = new TerminalManager(this);
1009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mHide = mPreferences.getBoolean(Constants.HIDE_NOTIFY, false);
1029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @Override
1059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  protected Notification createNotification() {
1069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Intent notificationIntent = new Intent(this, ScriptingLayerService.class);
1079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    notificationIntent.setAction(Constants.ACTION_SHOW_RUNNING_SCRIPTS);
1089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotificationPendingIntent = PendingIntent.getService(this, 0, notificationIntent, 0);
1099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Notification.Builder builder = new Notification.Builder(this);
1119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    builder.setSmallIcon(R.drawable.sl4a_notification_logo)
1129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setTicker(null)
1139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setWhen(System.currentTimeMillis())
1149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setContentTitle("SL4A Service")
1159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setContentText("Tap to view running scripts")
1169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setContentIntent(mNotificationPendingIntent);
1179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotification = builder.build();
1189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
1199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mNotification;
1209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void updateNotification(String tickerText) {
1239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (tickerText.equals(mNotification.tickerText)) {
1249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // Consequent notifications with the same ticker-text are displayed without any ticker-text.
1259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // This is a way around. Alternatively, we can display process name and port.
1269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      tickerText = tickerText + " ";
1279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    String msg;
1299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (mProcessMap.size() <= 1) {
1309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      msg = "Tap to view " + Integer.toString(mProcessMap.size()) + " running script";
1319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } else {
1329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      msg = "Tap to view " + Integer.toString(mProcessMap.size()) + " running scripts";
1339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Notification.Builder builder = new Notification.Builder(this);
1359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    builder.setContentTitle("SL4A Service")
1369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setContentText(msg)
1379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setContentIntent(mNotificationPendingIntent)
1389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setSmallIcon(mNotification.icon, mProcessMap.size())
1399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setWhen(mNotification.when)
1409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li           .setTicker(tickerText);
1419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotification = builder.build();
1439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mNotificationManager.notify(NOTIFICATION_ID, mNotification);
1449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
146b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold  private void startAction(Intent intent, int flags, int startId) {
147b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    AndroidProxy proxy = null;
148b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    InterpreterProcess interpreterProcess = null;
1499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    String errmsg = null;
1509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (intent == null) {
151b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    } else if (intent.getAction().equals(Constants.ACTION_KILL_ALL)) {
1529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      killAll();
1539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      stopSelf(startId);
154b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    } else if (intent.getAction().equals(Constants.ACTION_KILL_PROCESS)) {
1559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      killProcess(intent);
1569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (mProcessMap.isEmpty()) {
1579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        stopSelf(startId);
1589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
159b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    } else if (intent.getAction().equals(Constants.ACTION_SHOW_RUNNING_SCRIPTS)) {
1609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      showRunningScripts();
161b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    } else { //We are launching a script of some kind
162b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      if (intent.getAction().equals(Constants.ACTION_LAUNCH_SERVER)) {
163b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        proxy = launchServer(intent, false);
164b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        // TODO(damonkohler): This is just to make things easier. Really, we shouldn't need to start
165b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        // an interpreter when all we want is a server.
166b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        interpreterProcess = new InterpreterProcess(new ShellInterpreter(), proxy);
167b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        interpreterProcess.setName("Server");
168b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      }
169b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      else if (intent.getAction().equals(Constants.ACTION_LAUNCH_FOREGROUND_SCRIPT)) {
170b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        proxy = launchServer(intent, true);
1719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        launchTerminal(proxy.getAddress());
1729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        try {
1739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          interpreterProcess = launchScript(intent, proxy);
1749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        } catch (RuntimeException e) {
1759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          errmsg =
1769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li              "Unable to run " + intent.getStringExtra(Constants.EXTRA_SCRIPT_PATH) + "\n"
1779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li                  + e.getMessage();
1789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          interpreterProcess = null;
1799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
1809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (intent.getAction().equals(Constants.ACTION_LAUNCH_BACKGROUND_SCRIPT)) {
181b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        proxy = launchServer(intent, true);
1829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        interpreterProcess = launchScript(intent, proxy);
1839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (intent.getAction().equals(Constants.ACTION_LAUNCH_INTERPRETER)) {
184b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        proxy = launchServer(intent, true);
1859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        launchTerminal(proxy.getAddress());
1869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        interpreterProcess = launchInterpreter(intent, proxy);
1879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
188b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      if (interpreterProcess == null) {
189b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        errmsg = "Action not implemented: " + intent.getAction();
190b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      } else {
191b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        addProcess(interpreterProcess);
192b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold      }
1939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (errmsg != null) {
1959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      updateNotification(errmsg);
1969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
197b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold  }
198b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold
199b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold  @Override
200b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold  public int onStartCommand(Intent intent, int flags, int startId) {
201b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    super.onStartCommand(intent, flags, startId);
202b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold
203b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    //TODO: b/26538940 We need to go back to a strict policy and fix the problems
204b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    StrictMode.ThreadPolicy sl4aPolicy = new StrictMode.ThreadPolicy.Builder()
205b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        .detectAll()
206b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        .penaltyLog()
207b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        .build();
208b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    StrictMode.setThreadPolicy(sl4aPolicy);
209b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold
210b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    Thread launchThread = new Thread(new Runnable() {
211b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        public void run() {
212b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold            startAction(intent, flags, startId);
213b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        }
214b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    });
215b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    launchThread.start();
216b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold
2179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return START_REDELIVER_INTENT;
2189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private boolean tryPort(AndroidProxy androidProxy, boolean usePublicIp, int usePort) {
2219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (usePublicIp) {
2229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return (androidProxy.startPublic(usePort) != null);
2239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } else {
2249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return (androidProxy.startLocal(usePort) != null);
2259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private AndroidProxy launchServer(Intent intent, boolean requiresHandshake) {
2299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    AndroidProxy androidProxy = new AndroidProxy(this, intent, requiresHandshake);
2309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    boolean usePublicIp = intent.getBooleanExtra(Constants.EXTRA_USE_EXTERNAL_IP, false);
2319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    int usePort = intent.getIntExtra(Constants.EXTRA_USE_SERVICE_PORT, 0);
2329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    // If port is in use, fall back to default behaviour
2339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!tryPort(androidProxy, usePublicIp, usePort)) {
2349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (usePort != 0) {
2359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        tryPort(androidProxy, usePublicIp, 0);
2369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
2379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return androidProxy;
2399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private ScriptProcess launchScript(Intent intent, AndroidProxy proxy) {
2429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final int port = proxy.getAddress().getPort();
2439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    File script = new File(intent.getStringExtra(Constants.EXTRA_SCRIPT_PATH));
2449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return ScriptLauncher.launchScript(script, mInterpreterConfiguration, proxy, new Runnable() {
2459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      @Override
2469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      public void run() {
2479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        // TODO(damonkohler): This action actually kills the script rather than notifying the
2489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        // service that script exited on its own. We should distinguish between these two cases.
2499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        Intent intent = new Intent(ScriptingLayerService.this, ScriptingLayerService.class);
2509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        intent.setAction(Constants.ACTION_KILL_PROCESS);
2519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        intent.putExtra(Constants.EXTRA_PROXY_PORT, port);
2529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        startService(intent);
2539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
2549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    });
2559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private InterpreterProcess launchInterpreter(Intent intent, AndroidProxy proxy) {
2589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    InterpreterConfiguration config =
2599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        ((BaseApplication) getApplication()).getInterpreterConfiguration();
2609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final int port = proxy.getAddress().getPort();
2619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return ScriptLauncher.launchInterpreter(proxy, intent, config, new Runnable() {
2629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      @Override
2639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      public void run() {
2649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        // TODO(damonkohler): This action actually kills the script rather than notifying the
2659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        // service that script exited on its own. We should distinguish between these two cases.
2669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        Intent intent = new Intent(ScriptingLayerService.this, ScriptingLayerService.class);
2679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        intent.setAction(Constants.ACTION_KILL_PROCESS);
2689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        intent.putExtra(Constants.EXTRA_PROXY_PORT, port);
2699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        startService(intent);
2709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
2719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    });
2729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void launchTerminal(InetSocketAddress address) {
2759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Intent i = new Intent(this, ConsoleActivity.class);
2769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    i.putExtra(Constants.EXTRA_PROXY_PORT, address.getPort());
2789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    startActivity(i);
2799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void showRunningScripts() {
2829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Intent i = new Intent(this, ScriptProcessMonitor.class);
2839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    startActivity(i);
2859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void addProcess(InterpreterProcess process) {
288b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    synchronized(mProcessMap) {
289b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        mProcessMap.put(process.getPort(), process);
290b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        mModCount++;
291b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    }
2929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!mHide) {
2939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      updateNotification(process.getName() + " started.");
2949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private InterpreterProcess removeProcess(int port) {
298b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    InterpreterProcess process;
299b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold    synchronized(mProcessMap) {
300b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        process = mProcessMap.remove(port);
301b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        if (process == null) {
302b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold          return null;
303b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        }
304b3a2a85cfa37a83cf06e42fa6c50e4766e3afb09Nathan Harold        mModCount++;
3059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!mHide) {
3079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      updateNotification(process.getName() + " exited.");
3089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return process;
3109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void killProcess(Intent intent) {
3139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    int processId = intent.getIntExtra(Constants.EXTRA_PROXY_PORT, 0);
3149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    InterpreterProcess process = removeProcess(processId);
3159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (process != null) {
3169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      process.kill();
3179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      mRecentlyKilledProcess = new WeakReference<InterpreterProcess>(process);
3189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public int getModCount() {
3229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mModCount;
3239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private void killAll() {
3269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (InterpreterProcess process : getScriptProcessesList()) {
3279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      process = removeProcess(process.getPort());
3289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (process != null) {
3299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        process.kill();
3309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
3319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public List<InterpreterProcess> getScriptProcessesList() {
3359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    ArrayList<InterpreterProcess> result = new ArrayList<InterpreterProcess>();
3369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    result.addAll(mProcessMap.values());
3379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return result;
3389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public InterpreterProcess getProcess(int port) {
3419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    InterpreterProcess p = mProcessMap.get(port);
3429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (p == null) {
3439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return mRecentlyKilledProcess.get();
3449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return p;
3469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public TerminalManager getTerminalManager() {
3499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mTerminalManager;
3509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li}
352