• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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