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  */
     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