blob: b41fa62b5332a722054db10bffb04e134455dc36 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.vr.keyboard;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.RemoteException;
import com.google.vr.keyboard.IGvrKeyboardLoader;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
/** Loads the GVR keyboard SDK dynamically using the Keyboard Service. */
@JNINamespace("vr")
public class GvrKeyboardLoaderClient {
private static final String TAG = "ChromeGvrKbClient";
private static final boolean DEBUG_LOGS = false;
private static final String KEYBOARD_PACKAGE = "com.google.android.vr.inputmethod";
private static final String LOADER_NAME = "com.google.vr.keyboard.GvrKeyboardLoader";
private static IGvrKeyboardLoader sLoader = null;
private static ClassLoader sRemoteClassLoader = null;
// GVR doesn't support setting the context twice in the application's lifetime and crashes if we
// do so. Setting the same context wrapper is a no-op, so we keep a reference to the one we
// create and use it across re-initialization of the keyboard api.
@SuppressLint("StaticFieldLeak")
private static KeyboardContextWrapper sContextWrapper = null;
@CalledByNative
public static long loadKeyboardSDK() {
if (DEBUG_LOGS) Log.i(TAG, "loadKeyboardSDK");
IGvrKeyboardLoader loader = getLoader();
if (loader == null) {
if (DEBUG_LOGS) Log.i(TAG, "Couldn't find GVR keyboard SDK.");
return 0;
}
try {
long handle = loader.loadGvrKeyboard(BuildConstants.API_VERSION);
return handle;
} catch (RemoteException e) {
if (DEBUG_LOGS) Log.i(TAG, "Couldn't load GVR keyboard SDK.");
return 0;
}
}
@CalledByNative
public static void closeKeyboardSDK(long handle) {
if (DEBUG_LOGS) Log.i(TAG, "loadKeyboardSDK");
IGvrKeyboardLoader loader = getLoader();
if (loader != null) {
try {
loader.closeGvrKeyboard(handle);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't close GVR keyboard library", e);
}
}
}
private static IGvrKeyboardLoader getLoader() {
if (sLoader == null) {
ClassLoader remoteClassLoader = (ClassLoader) getRemoteClassLoader();
if (remoteClassLoader != null) {
IBinder binder = newBinder(remoteClassLoader, LOADER_NAME);
if (binder != null) sLoader = IGvrKeyboardLoader.Stub.asInterface(binder);
}
}
return sLoader;
}
private static Context getRemoteContext(Context context) {
try {
// The flags Context.CONTEXT_INCLUDE_CODE and Context.CONTEXT_IGNORE_SECURITY are
// needed to be able to load classes via the classloader of the returned context.
return context.createPackageContext(KEYBOARD_PACKAGE,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
} catch (NameNotFoundException e) {
Log.e(TAG, "Couldn't find remote context", e);
}
return null;
}
@CalledByNative
public static Context getContextWrapper() {
if (sContextWrapper != null) return sContextWrapper;
Context context = ContextUtils.getApplicationContext();
sContextWrapper = new KeyboardContextWrapper(getRemoteContext(context), context);
return sContextWrapper;
}
@CalledByNative
public static Object getRemoteClassLoader() {
Context context = ContextUtils.getApplicationContext();
if (sRemoteClassLoader == null) {
Context remoteContext = getRemoteContext(context);
if (remoteContext != null) {
sRemoteClassLoader = remoteContext.getClassLoader();
}
}
return sRemoteClassLoader;
}
private static IBinder newBinder(ClassLoader classLoader, String className) {
try {
Class<?> clazz = classLoader.loadClass(className);
return (IBinder) clazz.getConstructor().newInstance();
} catch (ClassNotFoundException e) {
// This could happen if the user has a really old version of the keyboard installed when
// dynamic loading was not supported.
Log.e(TAG, "Unable to find dynamic class " + className);
return null;
} catch (InstantiationException e) {
throw new IllegalStateException("Unable to instantiate the remote class " + className);
} catch (IllegalAccessException e) {
throw new IllegalStateException(
"Unable to call the default constructor of " + className);
} catch (Exception e) {
throw new IllegalStateException("Reflection error in " + className);
}
}
private static class KeyboardContextWrapper extends ContextWrapper {
private final Context mKeyboardContext;
private KeyboardContextWrapper(Context keyboardContext, Context baseContext) {
super(baseContext);
this.mKeyboardContext = keyboardContext;
}
@Override
public Object getSystemService(String name) {
// As the LAYOUT_INFLATER_SERVICE uses assets from the Context, it should point to the
// keyboard Context.
if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
return mKeyboardContext.getSystemService(name);
} else {
return super.getSystemService(name);
}
}
@Override
public Resources getResources() {
return mKeyboardContext.getResources();
}
@Override
public AssetManager getAssets() {
return mKeyboardContext.getAssets();
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public ClassLoader getClassLoader() {
return mKeyboardContext.getClassLoader();
}
}
}