/* * Copyright 2017-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.android.support.exopackage; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.util.ArrayMap; import android.util.Log; public class ResourcesLoader { private static final String TAG = "ResourcesLoader"; private ResourcesLoader() { } public static void init(Context context) { try { install(context); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } public static void install(Context context) throws Exception { File resourcesDirectory = new File( "/data/local/tmp/exopackage/" + context.getPackageName() + "/resources"); List<File> exoResourcePaths = readMetadata(resourcesDirectory); if (exoResourcePaths.isEmpty()) { Log.w(TAG, "No exo-resources found in: " + resourcesDirectory.getName()); return; } AssetManager assetManager = AssetManager.class.getConstructor().newInstance(); Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); addAssetPathMethod.setAccessible(true); for (File resourcePath : exoResourcePaths) { int cookie = (int) addAssetPathMethod.invoke(assetManager, resourcePath.getAbsolutePath()); if (cookie == 0) { throw new RuntimeException("Unable to add resources."); } } // Required on KitKat and doesn't hurt elsewhere. Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks"); mEnsureStringBlocks.setAccessible(true); mEnsureStringBlocks.invoke(assetManager); Collection<WeakReference<Resources>> activeResources = getActiveResources(); for (WeakReference<Resources> ref : activeResources) { Resources res = ref.get(); if (res == null) { continue; } try { Reflect.setField(res, Resources.class, "mAssets", assetManager); } catch (NoSuchFieldException e) { Object resourcesImpl = Reflect.getField(res, Resources.class, "mResourcesImpl"); Reflect.setField(resourcesImpl, resourcesImpl.getClass(), "mAssets", assetManager); } res.updateConfiguration(res.getConfiguration(), res.getDisplayMetrics()); } } private static Collection<WeakReference<Resources>> getActiveResources() { Collection<WeakReference<Resources>> activeResources = getActiveResourcesFromResourcesManager(); if (activeResources != null) { return activeResources; } return getActiveResourcesFromActivityThread(); } private static Collection<WeakReference<Resources>> getActiveResourcesFromActivityThread() { try { Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method m = activityThreadClass.getMethod("currentActivityThread"); m.setAccessible(true); Object currentActivityThread = m.invoke(null); return ((HashMap<?, WeakReference<Resources>>) Reflect.getField( currentActivityThread, activityThreadClass, "mActiveResources" )).values(); } catch (Exception e) { throw new RuntimeException(e); } } private static Collection<WeakReference<Resources>> getActiveResourcesFromResourcesManager() { try { Class resourcesManagerClass; try { resourcesManagerClass = Class.forName("android.app.ResourcesManager"); } catch (ClassNotFoundException e) { return null; } Method getResourcesManager = resourcesManagerClass.getDeclaredMethod("getInstance"); getResourcesManager.setAccessible(true); Object resourcesManager = getResourcesManager.invoke(null); try { return ((ArrayMap<?, WeakReference<Resources>>) Reflect.getField( resourcesManager, resourcesManagerClass, "mActiveResources")).values(); } catch (NoSuchFieldException e) { return (Collection<WeakReference<Resources>>) Reflect.getField( resourcesManager, resourcesManagerClass, "mResourceReferences" ); } } catch (Exception e) { throw new RuntimeException(e); } } private static List<File> readMetadata(File resourcesDirectory) throws IOException { BufferedReader br = null; try { br = new BufferedReader(new FileReader( new File(resourcesDirectory, "metadata.txt"))); ArrayList<File> resources = new ArrayList<>(); for (String line; (line = br.readLine()) != null; ) { String[] values = line.split(" "); if (values.length != 2) { throw new RuntimeException("Bad metadata for resources... (" + line + ")"); } if (!values[0].equals("resources")) { throw new RuntimeException("Unrecognized resource type: (" + line + ")"); } File apk = new File(resourcesDirectory, values[1] + ".apk"); if (!apk.exists()) { throw new RuntimeException("resources don't exist... (" + line + ")"); } resources.add(apk); } return resources; } finally { if (br != null) { br.close(); } } } }