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 43 import java.util.List; 44 import java.util.Timer; 45 import java.util.TimerTask; 46 47 import org.connectbot.ConsoleActivity; 48 49 /** 50 * An activity that allows to monitor running scripts. 51 * 52 */ 53 public class ScriptProcessMonitor extends ListActivity { 54 55 private final static int UPDATE_INTERVAL_SECS = 1; 56 57 private final Timer mTimer = new Timer(); 58 59 private volatile ScriptingLayerService mService; 60 61 private ScriptListAdapter mUpdater; 62 private List<InterpreterProcess> mProcessList; 63 private ScriptMonitorAdapter mAdapter; 64 private boolean mIsConnected = false; 65 66 private ServiceConnection mConnection = new ServiceConnection() { 67 @Override 68 public void onServiceConnected(ComponentName name, IBinder service) { 69 mService = ((ScriptingLayerService.LocalBinder) service).getService(); 70 mUpdater = new ScriptListAdapter(); 71 mTimer.scheduleAtFixedRate(mUpdater, 0, UPDATE_INTERVAL_SECS * 1000); 72 mIsConnected = true; 73 } 74 75 @Override 76 public void onServiceDisconnected(ComponentName name) { 77 mService = null; 78 mIsConnected = false; 79 mProcessList = null; 80 mAdapter.notifyDataSetChanged(); 81 } 82 }; 83 84 @Override 85 public void onCreate(Bundle savedInstanceState) { 86 super.onCreate(savedInstanceState); 87 bindService(new Intent(this, ScriptingLayerService.class), mConnection, 0); 88 CustomizeWindow.requestCustomTitle(this, "Script Monitor", R.layout.script_monitor); 89 mAdapter = new ScriptMonitorAdapter(); 90 setListAdapter(mAdapter); 91 registerForContextMenu(getListView()); 92 } 93 94 @Override 95 public void onPause() { 96 super.onPause(); 97 if (mUpdater != null) { 98 mUpdater.cancel(); 99 } 100 mTimer.purge(); 101 } 102 103 @Override 104 public void onResume() { 105 super.onResume(); 106 if (mIsConnected) { 107 try { 108 mUpdater = new ScriptListAdapter(); 109 mTimer.scheduleAtFixedRate(mUpdater, 0, UPDATE_INTERVAL_SECS * 1000); 110 } catch (IllegalStateException e) { 111 Log.e(e.getMessage(), e); 112 } 113 } 114 } 115 116 @Override 117 public void onDestroy() { 118 super.onDestroy(); 119 mTimer.cancel(); 120 unbindService(mConnection); 121 } 122 123 @Override 124 protected void onListItemClick(ListView list, View view, int position, long id) { 125 final InterpreterProcess script = (InterpreterProcess) list.getItemAtPosition(position); 126 Intent intent = new Intent(this, ConsoleActivity.class); 127 intent.putExtra(Constants.EXTRA_PROXY_PORT, script.getPort()); 128 startActivity(intent); 129 } 130 131 @Override 132 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 133 menu.add(Menu.NONE, 0, Menu.NONE, "Stop"); 134 } 135 136 @Override 137 public boolean onContextItemSelected(MenuItem item) { 138 AdapterView.AdapterContextMenuInfo info; 139 try { 140 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 141 } catch (ClassCastException e) { 142 Log.e("Bad menuInfo", e); 143 return false; 144 } 145 146 InterpreterProcess script = mAdapter.getItem(info.position); 147 if (script == null) { 148 Log.v("No script selected."); 149 return false; 150 } 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 startService(intent); 156 157 return true; 158 } 159 160 @Override 161 public boolean onPrepareOptionsMenu(Menu menu) { 162 menu.clear(); 163 // TODO(damonkohler): How could mProcessList ever be null? 164 if (mProcessList != null && !mProcessList.isEmpty()) { 165 menu.add(Menu.NONE, 0, Menu.NONE, R.string.stop_all).setIcon( 166 android.R.drawable.ic_menu_close_clear_cancel); 167 } 168 return super.onPrepareOptionsMenu(menu); 169 } 170 171 @Override 172 public boolean onOptionsItemSelected(MenuItem item) { 173 Intent intent = new Intent(this, ScriptingLayerService.class); 174 intent.setAction(Constants.ACTION_KILL_ALL); 175 startService(intent); 176 return true; 177 } 178 179 private class ScriptListAdapter extends TimerTask { 180 private int mmExpectedModCount = 0; 181 private volatile List<InterpreterProcess> mmList; 182 183 @Override 184 public void run() { 185 if (mService == null) { 186 mmList.clear(); 187 mTimer.cancel(); 188 } else { 189 int freshModCount = mService.getModCount(); 190 if (freshModCount != mmExpectedModCount) { 191 mmExpectedModCount = freshModCount; 192 mmList = mService.getScriptProcessesList(); 193 } 194 } 195 runOnUiThread(new Runnable() { 196 public void run() { 197 mProcessList = mUpdater.getFreshProcessList(); 198 mAdapter.notifyDataSetChanged(); 199 } 200 }); 201 } 202 203 private List<InterpreterProcess> getFreshProcessList() { 204 return mmList; 205 } 206 } 207 208 private class ScriptMonitorAdapter extends BaseAdapter { 209 210 @Override 211 public int getCount() { 212 if (mProcessList == null) { 213 return 0; 214 } 215 return mProcessList.size(); 216 } 217 218 @Override 219 public InterpreterProcess getItem(int position) { 220 return mProcessList.get(position); 221 } 222 223 @Override 224 public long getItemId(int position) { 225 return position; 226 } 227 228 @Override 229 public View getView(int position, View convertView, ViewGroup parent) { 230 View itemView; 231 if (convertView == null) { 232 LayoutInflater inflater = 233 (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 234 itemView = inflater.inflate(R.layout.script_monitor_list_item, parent, false); 235 } else { 236 itemView = convertView; 237 } 238 InterpreterProcess process = mProcessList.get(position); 239 ((TextView) itemView.findViewById(R.id.process_title)).setText(process.getName()); 240 ((TextView) itemView.findViewById(R.id.process_age)).setText(process.getUptime()); 241 ((TextView) itemView.findViewById(R.id.process_details)).setText(process.getHost() + ":" 242 + process.getPort()); 243 ((TextView) itemView.findViewById(R.id.process_status)).setText("PID " + process.getPid()); 244 return itemView; 245 } 246 } 247 } 248