1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.android_scripting.activity; 18 19 import android.app.ListActivity; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.Bundle; 25 import android.os.IBinder; 26 import android.view.ContextMenu; 27 import android.view.ContextMenu.ContextMenuInfo; 28 import android.view.LayoutInflater; 29 import android.view.Menu; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.AdapterView; 34 import android.widget.BaseAdapter; 35 import android.widget.ListView; 36 import android.widget.TextView; 37 38 import com.googlecode.android_scripting.Constants; 39 import com.googlecode.android_scripting.Log; 40 import com.googlecode.android_scripting.R; 41 import com.googlecode.android_scripting.interpreter.InterpreterProcess; 42 import com.googlecode.android_scripting.service.ScriptingLayerService; 43 44 import java.util.List; 45 import java.util.Timer; 46 import java.util.TimerTask; 47 48 import org.connectbot.ConsoleActivity; 49 50 /** 51 * An activity that allows to monitor running scripts. 52 * 53 */ 54 public class ScriptProcessMonitor extends ListActivity { 55 56 private final static int UPDATE_INTERVAL_SECS = 1; 57 58 private final Timer mTimer = new Timer(); 59 60 private volatile ScriptingLayerService mService; 61 62 private ScriptListAdapter mUpdater; 63 private List<InterpreterProcess> mProcessList; 64 private ScriptMonitorAdapter mAdapter; 65 private boolean mIsConnected = false; 66 67 private ServiceConnection mConnection = new ServiceConnection() { 68 @Override 69 public void onServiceConnected(ComponentName name, IBinder service) { 70 mService = ((ScriptingLayerService.LocalBinder) service).getService(); 71 mUpdater = new ScriptListAdapter(); 72 mTimer.scheduleAtFixedRate(mUpdater, 0, UPDATE_INTERVAL_SECS * 1000); 73 mIsConnected = true; 74 } 75 76 @Override 77 public void onServiceDisconnected(ComponentName name) { 78 mService = null; 79 mIsConnected = false; 80 mProcessList = null; 81 mAdapter.notifyDataSetChanged(); 82 } 83 }; 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 bindService(new Intent(this, ScriptingLayerService.class), mConnection, 0); 89 CustomizeWindow.requestCustomTitle(this, "Script Monitor", R.layout.script_monitor); 90 mAdapter = new ScriptMonitorAdapter(); 91 setListAdapter(mAdapter); 92 registerForContextMenu(getListView()); 93 } 94 95 @Override onPause()96 public void onPause() { 97 super.onPause(); 98 if (mUpdater != null) { 99 mUpdater.cancel(); 100 } 101 mTimer.purge(); 102 } 103 104 @Override onResume()105 public void onResume() { 106 super.onResume(); 107 if (mIsConnected) { 108 try { 109 mUpdater = new ScriptListAdapter(); 110 mTimer.scheduleAtFixedRate(mUpdater, 0, UPDATE_INTERVAL_SECS * 1000); 111 } catch (IllegalStateException e) { 112 Log.e(e.getMessage(), e); 113 } 114 } 115 } 116 117 @Override onDestroy()118 public void onDestroy() { 119 super.onDestroy(); 120 mTimer.cancel(); 121 unbindService(mConnection); 122 } 123 124 @Override onListItemClick(ListView list, View view, int position, long id)125 protected void onListItemClick(ListView list, View view, int position, long id) { 126 final InterpreterProcess script = (InterpreterProcess) list.getItemAtPosition(position); 127 Intent intent = new Intent(this, ConsoleActivity.class); 128 intent.putExtra(Constants.EXTRA_PROXY_PORT, script.getPort()); 129 startActivity(intent); 130 } 131 132 @Override onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)133 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 134 menu.add(Menu.NONE, 0, Menu.NONE, "Stop"); 135 } 136 137 @Override onContextItemSelected(MenuItem item)138 public boolean onContextItemSelected(MenuItem item) { 139 AdapterView.AdapterContextMenuInfo info; 140 try { 141 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 142 } catch (ClassCastException e) { 143 Log.e("Bad menuInfo", e); 144 return false; 145 } 146 147 InterpreterProcess script = mAdapter.getItem(info.position); 148 if (script == null) { 149 Log.v("No script selected."); 150 return false; 151 } 152 Intent intent = new Intent(ScriptProcessMonitor.this, ScriptingLayerService.class); 153 intent.setAction(Constants.ACTION_KILL_PROCESS); 154 intent.putExtra(Constants.EXTRA_PROXY_PORT, script.getPort()); 155 Log.i(String.format("Killing process from ScriptProcessMonitor: %s", intent.toUri(0))); 156 startService(intent); 157 158 return true; 159 } 160 161 @Override onPrepareOptionsMenu(Menu menu)162 public boolean onPrepareOptionsMenu(Menu menu) { 163 menu.clear(); 164 // TODO(damonkohler): How could mProcessList ever be null? 165 if (mProcessList != null && !mProcessList.isEmpty()) { 166 menu.add(Menu.NONE, 0, Menu.NONE, R.string.stop_all).setIcon( 167 android.R.drawable.ic_menu_close_clear_cancel); 168 } 169 return super.onPrepareOptionsMenu(menu); 170 } 171 172 @Override onOptionsItemSelected(MenuItem item)173 public boolean onOptionsItemSelected(MenuItem item) { 174 Intent intent = new Intent(this, ScriptingLayerService.class); 175 intent.setAction(Constants.ACTION_KILL_ALL); 176 startService(intent); 177 return true; 178 } 179 180 private class ScriptListAdapter extends TimerTask { 181 private int mmExpectedModCount = 0; 182 private volatile List<InterpreterProcess> mmList; 183 184 @Override run()185 public void run() { 186 if (mService == null) { 187 mmList.clear(); 188 mTimer.cancel(); 189 } else { 190 int freshModCount = mService.getModCount(); 191 if (freshModCount != mmExpectedModCount) { 192 mmExpectedModCount = freshModCount; 193 mmList = mService.getScriptProcessesList(); 194 } 195 } 196 runOnUiThread(new Runnable() { 197 public void run() { 198 mProcessList = mUpdater.getFreshProcessList(); 199 mAdapter.notifyDataSetChanged(); 200 } 201 }); 202 } 203 getFreshProcessList()204 private List<InterpreterProcess> getFreshProcessList() { 205 return mmList; 206 } 207 } 208 209 private class ScriptMonitorAdapter extends BaseAdapter { 210 211 @Override getCount()212 public int getCount() { 213 if (mProcessList == null) { 214 return 0; 215 } 216 return mProcessList.size(); 217 } 218 219 @Override getItem(int position)220 public InterpreterProcess getItem(int position) { 221 return mProcessList.get(position); 222 } 223 224 @Override getItemId(int position)225 public long getItemId(int position) { 226 return position; 227 } 228 229 @Override getView(int position, View convertView, ViewGroup parent)230 public View getView(int position, View convertView, ViewGroup parent) { 231 View itemView; 232 if (convertView == null) { 233 LayoutInflater inflater = 234 (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 235 itemView = inflater.inflate(R.layout.script_monitor_list_item, parent, false); 236 } else { 237 itemView = convertView; 238 } 239 InterpreterProcess process = mProcessList.get(position); 240 ((TextView) itemView.findViewById(R.id.process_title)).setText(process.getName()); 241 ((TextView) itemView.findViewById(R.id.process_age)).setText(process.getUptime()); 242 ((TextView) itemView.findViewById(R.id.process_details)).setText(process.getHost() + ":" 243 + process.getPort()); 244 ((TextView) itemView.findViewById(R.id.process_status)).setText("PID " + process.getPid()); 245 return itemView; 246 } 247 } 248 } 249