Sunday, 24 May 2015

Modifying OppoCamera's Plug ins

While fixing live effects in face beauty plug in, i needed to hook into Facebeauty plug in's APK (though i did it in past while debugging another plug ins, but i couldn't get time to write tutorial for it). Oppo has well written framework for loading plug ins. It uses DexClassLoader to load dex files at run time which interacts with camera app through predefined interfaces (defined in com.oppo.camera.capmode.CapModeBase). Each plug in's main class extends CapModeBase class and overrides needed methods. All plug ins have jar file included within them which are loaded at runtime

Now there are two ways to hook into Plug in:

  1. Using Xposed Framework (Fairly simple)
  2. Directly injecting code into smali

Using Xposed Framework:

Using Xposed to hook into plug in is very simple. All plug ins are loaded/managed by class com.oppo.camera.Plugin.PluginManager. Every time a plug in in installed, this class sets up environment to run that plug in by extracting libs and jar files. Have a look at following code

public static Map<String, ClassLoader> mClassLoaderIsLoaded;

private void updatePluginClassLoader(Plugin plugin) {
     DexClassLoader dexClassLoader = new DexClassLoader(modeJar.toString(), this.mContext.getFilesDir().getAbsolutePath(), libraryPath, this.mContext.getClassLoader());
     try {
          mClassLoaderIsLoaded.put(plugin.getCameraMode(), dexClassLoader);
          Constructor myConstructor = dexClassLoader.loadClass(plugin.getClassPackageName() + "." +      plugin.getCameraModeClassName()).getDeclaredConstructor(new Class[]{Activity.class, CameraInterface.class, CameraUIInterface.class});
          myConstructor.setAccessible(true);
          this.mPluginManagerListener.updateCameraMode((CapModeBase) myConstructor.newInstance(new Object[]{(Activity)      this.mContext, this.mCameraInterface, this.mCameraUIInterface}));
     } catch (Exception e3) {
          e2 = e3;
          DexClassLoader dexClassLoader2 = dexClassLoader;
     }
}
On startup, updatePluginClassLoader() method puts all classloaders for plug ins into HashSet mClassLoaderIsLoaded. So you first need to hook into PluginManager class to retrieve mClassLoaderIsLoaded. This can be done easily by
Map<String, ClassLoader> loaders = (Map) XposedHelpers.getObjectField(param.thisObject, "mClassLoaderIsLoaded");
Now you can retrieve plugin specific class loader by get method of Map like following snippet shows retrieval of classloader of Professional Camera Mode plug in
ClassLoader dexLoader = (DexClassLoader) loaders.get("professional");
Once we have classloader we can use it to hook into many class of target plug in like
Class manualFocusController = XposedHelpers.findClass("com.oppo.camera.professional.ManualFocusController", dexLoader);
You can find whole example code on pastebin

Directly Injecting smali code:

One problem is CameraApp is not loading plug in apk directly, instead it loads its jar file from assets directory. Code contained in jar file and classes.dex file are same. As usual we decompile classes.dex file and inject code into smali but things are a bit different here because classes.dex is never loaded, it is just placebo (may be because Android does not allow APKs without classes.dex?). So modifying smali code after normal decompilation will have no effect at run time. For that we need to decompile jar file included in assets directory. After modifying it, we also need to sign that jar file using same key used to sign APK file.