Home | History | Annotate | Download | only in interpreter
      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 {
     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 
     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 
     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 
     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 
    108     private void notifyConfigurationObservers() {
    109       for (ConfigurationObserver observer : mObserverSet) {
    110         observer.onConfigurationChanged();
    111       }
    112     }
    113 
    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 
    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.
    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 
    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
    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 
    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 
    228   public void startDiscovering() {
    229     mListener.discoverAll();
    230   }
    231 
    232   public void startDiscovering(String mime) {
    233     mListener.discoverForType(mime);
    234   }
    235 
    236   public boolean isDiscoveryComplete() {
    237     return mIsDiscoveryComplete;
    238   }
    239 
    240   public void registerObserver(ConfigurationObserver observer) {
    241     if (observer != null) {
    242       mObserverSet.add(observer);
    243     }
    244   }
    245 
    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    */
    255   public List<? extends Interpreter> getSupportedInterpreters() {
    256     return new ArrayList<Interpreter>(mInterpreterSet);
    257   }
    258 
    259   /**
    260    * Returns the list of all installed interpreters.
    261    */
    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    */
    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    */
    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    */
    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