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