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