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.app.SearchManager; 21 import android.content.Intent; 22 import android.database.MatrixCursor; 23 import android.os.Bundle; 24 import android.util.TypedValue; 25 import android.view.ContextMenu; 26 import android.view.KeyEvent; 27 import android.view.Menu; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ContextMenu.ContextMenuInfo; 32 import android.widget.AdapterView; 33 import android.widget.AlphabetIndexer; 34 import android.widget.BaseAdapter; 35 import android.widget.ListView; 36 import android.widget.SectionIndexer; 37 import android.widget.TextView; 38 39 import com.google.common.base.Predicate; 40 import com.google.common.collect.Collections2; 41 import com.google.common.collect.Lists; 42 import com.googlecode.android_scripting.BaseApplication; 43 import com.googlecode.android_scripting.Constants; 44 import com.googlecode.android_scripting.Log; 45 import com.googlecode.android_scripting.R; 46 import com.googlecode.android_scripting.facade.FacadeConfiguration; 47 import com.googlecode.android_scripting.interpreter.Interpreter; 48 import com.googlecode.android_scripting.interpreter.InterpreterConfiguration; 49 import com.googlecode.android_scripting.language.SupportedLanguages; 50 import com.googlecode.android_scripting.rpc.MethodDescriptor; 51 import com.googlecode.android_scripting.rpc.ParameterDescriptor; 52 import com.googlecode.android_scripting.rpc.RpcDeprecated; 53 import com.googlecode.android_scripting.rpc.RpcMinSdk; 54 55 import java.lang.reflect.Method; 56 import java.util.HashSet; 57 import java.util.List; 58 import java.util.Set; 59 60 public class ApiBrowser extends ListActivity { 61 62 private boolean searchResultMode = false; 63 64 private static enum RequestCode { 65 RPC_PROMPT 66 } 67 68 private static enum MenuId { 69 EXPAND_ALL, COLLAPSE_ALL, SEARCH; getId()70 public int getId() { 71 return ordinal() + Menu.FIRST; 72 } 73 } 74 75 private static enum ContextMenuId { 76 INSERT_TEXT, PROMPT_PARAMETERS, HELP; getId()77 public int getId() { 78 return ordinal() + Menu.FIRST; 79 } 80 } 81 82 private List<MethodDescriptor> mMethodDescriptors; 83 private Set<Integer> mExpandedPositions; 84 private ApiBrowserAdapter mAdapter; 85 private boolean mIsLanguageSupported; 86 87 @Override onCreate(Bundle savedInstanceState)88 public void onCreate(Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 CustomizeWindow.requestCustomTitle(this, "API Browser", R.layout.api_browser); 91 getListView().setFastScrollEnabled(true); 92 mExpandedPositions = new HashSet<Integer>(); 93 updateAndFilterMethodDescriptors(null); 94 String scriptName = getIntent().getStringExtra(Constants.EXTRA_SCRIPT_PATH); 95 mIsLanguageSupported = SupportedLanguages.checkLanguageSupported(scriptName); 96 mAdapter = new ApiBrowserAdapter(); 97 setListAdapter(mAdapter); 98 registerForContextMenu(getListView()); 99 setResult(RESULT_CANCELED); 100 } 101 updateAndFilterMethodDescriptors(final String query)102 private void updateAndFilterMethodDescriptors(final String query) { 103 mMethodDescriptors = 104 Lists.newArrayList(Collections2.filter(FacadeConfiguration.collectMethodDescriptors(), 105 new Predicate<MethodDescriptor>() { 106 @Override 107 public boolean apply(MethodDescriptor descriptor) { 108 Method method = descriptor.getMethod(); 109 if (method.isAnnotationPresent(RpcDeprecated.class)) { 110 return false; 111 } else if (method.isAnnotationPresent(RpcMinSdk.class)) { 112 int requiredSdkLevel = method.getAnnotation(RpcMinSdk.class).value(); 113 if (FacadeConfiguration.getSdkLevel() < requiredSdkLevel) { 114 return false; 115 } 116 } 117 if (query == null) { 118 return true; 119 } 120 return descriptor.getName().toLowerCase().contains(query.toLowerCase()); 121 } 122 })); 123 } 124 125 @Override onNewIntent(Intent intent)126 protected void onNewIntent(Intent intent) { 127 if (Intent.ACTION_SEARCH.equals(intent.getAction())) { 128 searchResultMode = true; 129 final String query = intent.getStringExtra(SearchManager.QUERY); 130 ((TextView) findViewById(R.id.left_text)).setText(query); 131 updateAndFilterMethodDescriptors(query); 132 if (mMethodDescriptors.size() == 1) { 133 mExpandedPositions.add(0); 134 } else { 135 mExpandedPositions.clear(); 136 } 137 mAdapter.notifyDataSetChanged(); 138 } 139 } 140 141 @Override onKeyDown(int keyCode, KeyEvent event)142 public boolean onKeyDown(int keyCode, KeyEvent event) { 143 if (keyCode == KeyEvent.KEYCODE_BACK && searchResultMode) { 144 searchResultMode = false; 145 mExpandedPositions.clear(); 146 ((TextView) findViewById(R.id.left_text)).setText("API Browser"); 147 updateAndFilterMethodDescriptors(""); 148 mAdapter.notifyDataSetChanged(); 149 return true; 150 } 151 return super.onKeyDown(keyCode, event); 152 } 153 154 @Override onPrepareOptionsMenu(Menu menu)155 public boolean onPrepareOptionsMenu(Menu menu) { 156 super.onPrepareOptionsMenu(menu); 157 menu.clear(); 158 menu.add(Menu.NONE, MenuId.EXPAND_ALL.getId(), Menu.NONE, "Expand All").setIcon( 159 android.R.drawable.ic_menu_add); 160 menu.add(Menu.NONE, MenuId.COLLAPSE_ALL.getId(), Menu.NONE, "Collapse All").setIcon( 161 android.R.drawable.ic_menu_close_clear_cancel); 162 menu.add(Menu.NONE, MenuId.SEARCH.getId(), Menu.NONE, "Search").setIcon( 163 R.drawable.ic_menu_search); 164 return true; 165 } 166 167 @Override onOptionsItemSelected(MenuItem item)168 public boolean onOptionsItemSelected(MenuItem item) { 169 super.onOptionsItemSelected(item); 170 int itemId = item.getItemId(); 171 if (itemId == MenuId.EXPAND_ALL.getId()) { 172 for (int i = 0; i < mMethodDescriptors.size(); i++) { 173 mExpandedPositions.add(i); 174 } 175 } else if (itemId == MenuId.COLLAPSE_ALL.getId()) { 176 mExpandedPositions.clear(); 177 } else if (itemId == MenuId.SEARCH.getId()) { 178 onSearchRequested(); 179 } 180 181 mAdapter.notifyDataSetInvalidated(); 182 return true; 183 } 184 185 @Override onListItemClick(ListView l, View v, int position, long id)186 protected void onListItemClick(ListView l, View v, int position, long id) { 187 if (mExpandedPositions.contains(position)) { 188 mExpandedPositions.remove(position); 189 } else { 190 mExpandedPositions.add(position); 191 } 192 mAdapter.notifyDataSetInvalidated(); 193 } 194 195 @Override onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)196 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 197 if (!mIsLanguageSupported) { 198 return; 199 } 200 menu.add(Menu.NONE, ContextMenuId.INSERT_TEXT.getId(), Menu.NONE, "Insert"); 201 menu.add(Menu.NONE, ContextMenuId.PROMPT_PARAMETERS.getId(), Menu.NONE, "Prompt"); 202 } 203 204 @Override onContextItemSelected(MenuItem item)205 public boolean onContextItemSelected(MenuItem item) { 206 AdapterView.AdapterContextMenuInfo info; 207 try { 208 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 209 } catch (ClassCastException e) { 210 Log.e("Bad menuInfo", e); 211 return false; 212 } 213 214 MethodDescriptor rpc = (MethodDescriptor) getListAdapter().getItem(info.position); 215 if (rpc == null) { 216 Log.v("No RPC selected."); 217 return false; 218 } 219 220 if (item.getItemId() == ContextMenuId.INSERT_TEXT.getId()) { 221 // There's no activity to track calls to insert (like there is for prompt) so we track it 222 // here instead. 223 insertText(rpc, new String[0]); 224 } else if (item.getItemId() == ContextMenuId.PROMPT_PARAMETERS.getId()) { 225 Intent intent = new Intent(this, ApiPrompt.class); 226 intent.putExtra(Constants.EXTRA_API_PROMPT_RPC_NAME, rpc.getName()); 227 ParameterDescriptor[] parameters = rpc.getParameterValues(new String[0]); 228 String[] values = new String[parameters.length]; 229 int index = 0; 230 for (ParameterDescriptor parameter : parameters) { 231 values[index++] = parameter.getValue(); 232 } 233 intent.putExtra(Constants.EXTRA_API_PROMPT_VALUES, values); 234 startActivityForResult(intent, RequestCode.RPC_PROMPT.ordinal()); 235 } else if (item.getItemId() == ContextMenuId.HELP.getId()) { 236 String help = rpc.getDeclaringClass().getSimpleName() + ".html#" + rpc.getName(); 237 238 } 239 return true; 240 } 241 242 @Override onActivityResult(int requestCode, int resultCode, Intent data)243 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 244 super.onActivityResult(requestCode, resultCode, data); 245 RequestCode request = RequestCode.values()[requestCode]; 246 if (resultCode == RESULT_OK) { 247 switch (request) { 248 case RPC_PROMPT: 249 MethodDescriptor rpc = 250 FacadeConfiguration.getMethodDescriptor(data 251 .getStringExtra(Constants.EXTRA_API_PROMPT_RPC_NAME)); 252 String[] values = data.getStringArrayExtra(Constants.EXTRA_API_PROMPT_VALUES); 253 insertText(rpc, values); 254 break; 255 default: 256 break; 257 } 258 } else { 259 switch (request) { 260 case RPC_PROMPT: 261 break; 262 default: 263 break; 264 } 265 } 266 } 267 insertText(MethodDescriptor rpc, String[] values)268 private void insertText(MethodDescriptor rpc, String[] values) { 269 String scriptText = getIntent().getStringExtra(Constants.EXTRA_SCRIPT_TEXT); 270 InterpreterConfiguration config = 271 ((BaseApplication) getApplication()).getInterpreterConfiguration(); 272 273 Interpreter interpreter = 274 config.getInterpreterByName(getIntent().getStringExtra(Constants.EXTRA_INTERPRETER_NAME)); 275 String rpcHelpText = interpreter.getRpcText(scriptText, rpc, values); 276 277 Intent intent = new Intent(); 278 intent.putExtra(Constants.EXTRA_RPC_HELP_TEXT, rpcHelpText); 279 setResult(RESULT_OK, intent); 280 finish(); 281 } 282 283 private class ApiBrowserAdapter extends BaseAdapter implements SectionIndexer { 284 285 private final AlphabetIndexer mIndexer; 286 private final MatrixCursor mCursor; 287 ApiBrowserAdapter()288 public ApiBrowserAdapter() { 289 mCursor = new MatrixCursor(new String[] { "NAME" }); 290 for (MethodDescriptor info : mMethodDescriptors) { 291 mCursor.addRow(new String[] { info.getName() }); 292 } 293 mIndexer = new AlphabetIndexer(mCursor, 0, " ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 294 } 295 296 @Override getCount()297 public int getCount() { 298 return mMethodDescriptors.size(); 299 } 300 301 @Override getItem(int position)302 public Object getItem(int position) { 303 return mMethodDescriptors.get(position); 304 } 305 306 @Override getItemId(int position)307 public long getItemId(int position) { 308 return position; 309 } 310 311 @Override getView(final int position, View convertView, ViewGroup parent)312 public View getView(final int position, View convertView, ViewGroup parent) { 313 TextView view; 314 if (convertView == null) { 315 view = new TextView(ApiBrowser.this); 316 } else { 317 view = (TextView) convertView; 318 } 319 view.setPadding(4, 4, 4, 4); 320 view.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); 321 if (mExpandedPositions.contains(position)) { 322 view.setText(mMethodDescriptors.get(position).getHelp()); 323 } else { 324 view.setText(mMethodDescriptors.get(position).getName()); 325 } 326 return view; 327 } 328 329 @Override getPositionForSection(int section)330 public int getPositionForSection(int section) { 331 return mIndexer.getPositionForSection(section); 332 } 333 334 @Override getSectionForPosition(int position)335 public int getSectionForPosition(int position) { 336 return mIndexer.getSectionForPosition(position); 337 } 338 339 @Override getSections()340 public Object[] getSections() { 341 return mIndexer.getSections(); 342 } 343 } 344 } 345