1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.interpreter; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ProviderInfo; 27 import android.content.pm.ResolveInfo; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.database.Cursor; 30 import android.net.Uri; 31 32 import com.googlecode.android_scripting.Log; 33 import com.googlecode.android_scripting.SingleThreadExecutor; 34 import com.googlecode.android_scripting.interpreter.shell.ShellInterpreter; 35 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.LinkedHashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.concurrent.CopyOnWriteArraySet; 44 import java.util.concurrent.ExecutorService; 45 46 /** 47 * Manages and provides access to the set of available interpreters. 48 * 49 */ 50 public class InterpreterConfiguration { 51 52 private final InterpreterListener mListener; 53 private final Set<Interpreter> mInterpreterSet; 54 private final Set<ConfigurationObserver> mObserverSet; 55 private final Context mContext; 56 private volatile boolean mIsDiscoveryComplete = false; 57 58 public interface ConfigurationObserver { 59 public void onConfigurationChanged(); 60 } 61 62 private class InterpreterListener extends BroadcastReceiver { 63 private final PackageManager mmPackageManager; 64 private final ContentResolver mmResolver; 65 private final ExecutorService mmExecutor; 66 private final Map<String, Interpreter> mmDiscoveredInterpreters; 67 68 private InterpreterListener(Context context) { 69 mmPackageManager = context.getPackageManager(); 70 mmResolver = context.getContentResolver(); 71 mmExecutor = new SingleThreadExecutor(); 72 mmDiscoveredInterpreters = new HashMap<String, Interpreter>(); 73 } 74 75 private void discoverForType(final String mime) { 76 mmExecutor.execute(new Runnable() { 77 @Override 78 public void run() { 79 Intent intent = new Intent(InterpreterConstants.ACTION_DISCOVER_INTERPRETERS); 80 intent.addCategory(Intent.CATEGORY_LAUNCHER); 81 intent.setType(mime); 82 List<ResolveInfo> resolveInfos = mmPackageManager.queryIntentActivities(intent, 0); 83 for (ResolveInfo info : resolveInfos) { 84 addInterpreter(info.activityInfo.packageName); 85 } 86 mIsDiscoveryComplete = true; 87 notifyConfigurationObservers(); 88 } 89 }); 90 } 91 92 private void discoverAll() { 93 mmExecutor.execute(new Runnable() { 94 @Override 95 public void run() { 96 Intent intent = new Intent(InterpreterConstants.ACTION_DISCOVER_INTERPRETERS); 97 intent.addCategory(Intent.CATEGORY_LAUNCHER); 98 intent.setType(InterpreterConstants.MIME + "*"); 99 List<ResolveInfo> resolveInfos = mmPackageManager.queryIntentActivities(intent, 0); 100 for (ResolveInfo info : resolveInfos) { 101 addInterpreter(info.activityInfo.packageName); 102 } 103 mIsDiscoveryComplete = true; 104 notifyConfigurationObservers(); 105 } 106 }); 107 } 108 109 private void notifyConfigurationObservers() { 110 for (ConfigurationObserver observer : mObserverSet) { 111 observer.onConfigurationChanged(); 112 } 113 } 114 115 private void addInterpreter(final String packageName) { 116 if (mmDiscoveredInterpreters.containsKey(packageName)) { 117 return; 118 } 119 Interpreter discoveredInterpreter = buildInterpreter(packageName); 120 if (discoveredInterpreter == null) { 121 return; 122 } 123 mmDiscoveredInterpreters.put(packageName, discoveredInterpreter); 124 mInterpreterSet.add(discoveredInterpreter); 125 Log.v("Interpreter discovered: " + packageName + "\nBinary: " 126 + discoveredInterpreter.getBinary()); 127 } 128 129 private void remove(final String packageName) { 130 if (!mmDiscoveredInterpreters.containsKey(packageName)) { 131 return; 132 } 133 mmExecutor.execute(new Runnable() { 134 @Override 135 public void run() { 136 Interpreter interpreter = mmDiscoveredInterpreters.get(packageName); 137 if (interpreter == null) { 138 Log.v("Interpreter for " + packageName + " not installed."); 139 return; 140 } 141 mInterpreterSet.remove(interpreter); 142 mmDiscoveredInterpreters.remove(packageName); 143 notifyConfigurationObservers(); 144 } 145 }); 146 } 147 148 // We require that there's only one interpreter provider per APK. 149 private Interpreter buildInterpreter(String packageName) { 150 PackageInfo packInfo; 151 try { 152 packInfo = mmPackageManager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS); 153 } catch (NameNotFoundException e) { 154 throw new RuntimeException("Package '" + packageName + "' not found."); 155 } 156 ProviderInfo provider = packInfo.providers[0]; 157 158 Map<String, String> interpreterMap = 159 getMap(provider, InterpreterConstants.PROVIDER_PROPERTIES); 160 if (interpreterMap == null) { 161 Log.e("Null interpreter map for: " + packageName); 162 return null; 163 } 164 Map<String, String> environmentMap = 165 getMap(provider, InterpreterConstants.PROVIDER_ENVIRONMENT_VARIABLES); 166 if (environmentMap == null) { 167 throw new RuntimeException("Null environment map for: " + packageName); 168 } 169 Map<String, String> argumentsMap = getMap(provider, InterpreterConstants.PROVIDER_ARGUMENTS); 170 if (argumentsMap == null) { 171 throw new RuntimeException("Null arguments map for: " + packageName); 172 } 173 return Interpreter.buildFromMaps(interpreterMap, environmentMap, argumentsMap); 174 } 175 176 private Map<String, String> getMap(ProviderInfo provider, String name) { 177 Uri uri = Uri.parse("content://" + provider.authority + "/" + name); 178 Cursor cursor = mmResolver.query(uri, null, null, null, null); 179 if (cursor == null) { 180 return null; 181 } 182 cursor.moveToFirst(); 183 // Use LinkedHashMap so that order is maintained (important for position CLI arguments). 184 Map<String, String> map = new LinkedHashMap<String, String>(); 185 for (int i = 0; i < cursor.getColumnCount(); i++) { 186 map.put(cursor.getColumnName(i), cursor.getString(i)); 187 } 188 return map; 189 } 190 191 @Override 192 public void onReceive(Context context, Intent intent) { 193 final String action = intent.getAction(); 194 final String packageName = intent.getData().getSchemeSpecificPart(); 195 if (action.equals(InterpreterConstants.ACTION_INTERPRETER_ADDED)) { 196 mmExecutor.execute(new Runnable() { 197 @Override 198 public void run() { 199 addInterpreter(packageName); 200 notifyConfigurationObservers(); 201 } 202 }); 203 } else if (action.equals(InterpreterConstants.ACTION_INTERPRETER_REMOVED) 204 || action.equals(Intent.ACTION_PACKAGE_REMOVED) 205 || action.equals(Intent.ACTION_PACKAGE_REPLACED) 206 || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) { 207 remove(packageName); 208 } 209 } 210 211 } 212 213 public InterpreterConfiguration(Context context) { 214 mContext = context; 215 mInterpreterSet = new CopyOnWriteArraySet<Interpreter>(); 216 mInterpreterSet.add(new ShellInterpreter()); 217 mObserverSet = new CopyOnWriteArraySet<ConfigurationObserver>(); 218 IntentFilter filter = new IntentFilter(); 219 filter.addAction(InterpreterConstants.ACTION_INTERPRETER_ADDED); 220 filter.addAction(InterpreterConstants.ACTION_INTERPRETER_REMOVED); 221 filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 222 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 223 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 224 filter.addDataScheme("package"); 225 mListener = new InterpreterListener(mContext); 226 mContext.registerReceiver(mListener, filter); 227 } 228 229 public void startDiscovering() { 230 mListener.discoverAll(); 231 } 232 233 public void startDiscovering(String mime) { 234 mListener.discoverForType(mime); 235 } 236 237 public boolean isDiscoveryComplete() { 238 return mIsDiscoveryComplete; 239 } 240 241 public void registerObserver(ConfigurationObserver observer) { 242 if (observer != null) { 243 mObserverSet.add(observer); 244 } 245 } 246 247 public void unregisterObserver(ConfigurationObserver observer) { 248 if (observer != null) { 249 mObserverSet.remove(observer); 250 } 251 } 252 253 /** 254 * Returns the list of all known interpreters. 255 */ 256 public List<? extends Interpreter> getSupportedInterpreters() { 257 return new ArrayList<Interpreter>(mInterpreterSet); 258 } 259 260 /** 261 * Returns the list of all installed interpreters. 262 */ 263 public List<Interpreter> getInstalledInterpreters() { 264 List<Interpreter> interpreters = new ArrayList<Interpreter>(); 265 for (Interpreter i : mInterpreterSet) { 266 if (i.isInstalled()) { 267 interpreters.add(i); 268 } 269 } 270 return interpreters; 271 } 272 273 /** 274 * Returns the list of interpreters that support interactive mode execution. 275 */ 276 public List<Interpreter> getInteractiveInterpreters() { 277 List<Interpreter> interpreters = new ArrayList<Interpreter>(); 278 for (Interpreter i : mInterpreterSet) { 279 if (i.isInstalled() && i.hasInteractiveMode()) { 280 interpreters.add(i); 281 } 282 } 283 return interpreters; 284 } 285 286 /** 287 * Returns the interpreter matching the provided name or null if no interpreter was found. 288 */ 289 public Interpreter getInterpreterByName(String interpreterName) { 290 for (Interpreter i : mInterpreterSet) { 291 if (i.getName().equals(interpreterName)) { 292 return i; 293 } 294 } 295 return null; 296 } 297 298 /** 299 * Returns the correct interpreter for the provided script name based on the script's extension or 300 * null if no interpreter was found. 301 */ 302 public Interpreter getInterpreterForScript(String scriptName) { 303 int dotIndex = scriptName.lastIndexOf('.'); 304 if (dotIndex == -1) { 305 return null; 306 } 307 String ext = scriptName.substring(dotIndex); 308 for (Interpreter i : mInterpreterSet) { 309 if (i.getExtension().equals(ext)) { 310 return i; 311 } 312 } 313 return null; 314 } 315 } 316