blob: aa15a9ccaf1b0d8ef8ec3e82c5c5a9635b63c25d [file] [log] [blame]
// Copyright 2012 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.ui.base;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityManager;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.ui.VSyncMonitor;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
/**
* The window base class that has the minimum functionality.
*/
@JNINamespace("ui")
public class WindowAndroid {
private static final String TAG = "WindowAndroid";
@TargetApi(Build.VERSION_CODES.KITKAT)
private class TouchExplorationMonitor {
// Listener that tells us when touch exploration is enabled or disabled.
private AccessibilityManager.TouchExplorationStateChangeListener mTouchExplorationListener;
TouchExplorationMonitor() {
mTouchExplorationListener =
new AccessibilityManager.TouchExplorationStateChangeListener() {
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
mIsTouchExplorationEnabled =
mAccessibilityManager.isTouchExplorationEnabled();
refreshWillNotDraw();
}
};
mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener);
}
void destroy() {
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationListener);
}
}
// Native pointer to the c++ WindowAndroid object.
private long mNativeWindowAndroid;
private final VSyncMonitor mVSyncMonitor;
private final DisplayAndroid mDisplayAndroid;
// A string used as a key to store intent errors in a bundle
static final String WINDOW_CALLBACK_ERRORS = "window_callback_errors";
// Error code returned when an Intent fails to start an Activity.
public static final int START_INTENT_FAILURE = -1;
protected Context mApplicationContext;
protected SparseArray<IntentCallback> mOutstandingIntents;
// We use a weak reference here to prevent this from leaking in WebView.
private WeakReference<Context> mContextRef;
// Ideally, this would be a SparseArray<String>, but there's no easy way to store a
// SparseArray<String> in a bundle during saveInstanceState(). So we use a HashMap and suppress
// the Android lint warning "UseSparseArrays".
protected HashMap<Integer, String> mIntentErrors;
// We track all animations over content and provide a drawing placeholder for them.
private HashSet<Animator> mAnimationsOverContent = new HashSet<>();
private View mAnimationPlaceholderView;
private ViewGroup mKeyboardAccessoryView;
protected boolean mIsKeyboardShowing;
// System accessibility service.
private final AccessibilityManager mAccessibilityManager;
// Whether touch exploration is enabled.
private boolean mIsTouchExplorationEnabled;
// On KitKat and higher, a class that monitors the touch exploration state.
private TouchExplorationMonitor mTouchExplorationMonitor;
private AndroidPermissionDelegate mPermissionDelegate;
// Note that this state lives in Java, rather than in the native BeginFrameSource because
// clients may pause VSync before the native WindowAndroid is created.
private boolean mPendingVSyncRequest;
private boolean mVSyncPaused;
/**
* An interface to notify listeners of changes in the soft keyboard's visibility.
*/
public interface KeyboardVisibilityListener {
public void keyboardVisibilityChanged(boolean isShowing);
}
private LinkedList<KeyboardVisibilityListener> mKeyboardVisibilityListeners =
new LinkedList<>();
/**
* An interface to notify listeners that a context menu is closed.
*/
public interface OnCloseContextMenuListener {
/**
* Called when a context menu has been closed.
*/
void onContextMenuClosed();
}
private final ObserverList<OnCloseContextMenuListener> mContextMenuCloseListeners =
new ObserverList<>();
private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() {
@Override
public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
if (mVSyncPaused) {
mPendingVSyncRequest = true;
return;
}
if (mNativeWindowAndroid != 0) {
nativeOnVSync(mNativeWindowAndroid,
vsyncTimeMicros,
mVSyncMonitor.getVSyncPeriodInMicroseconds());
}
}
};
/**
* Extract the activity if the given Context either is or wraps one.
* Only retrieve the base context if the supplied context is a {@link ContextWrapper} but not
* an Activity, given that Activity is already a subclass of ContextWrapper.
* @param context The context to check.
* @return The {@link Activity} that is extracted through the given Context.
*/
public static Activity activityFromContext(Context context) {
if (context instanceof Activity) {
return ((Activity) context);
} else if (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
return activityFromContext(context);
} else {
return null;
}
}
/**
* @return true if onVSync handler is executing.
*
* @see org.chromium.ui.VSyncMonitor#isInsideVSync()
*/
public boolean isInsideVSync() {
return mVSyncMonitor.isInsideVSync();
}
/**
* @return The time interval between two consecutive vsync pulses in milliseconds.
*/
public long getVsyncPeriodInMillis() {
return mVSyncMonitor.getVSyncPeriodInMicroseconds() / 1000;
}
/**
* @param context The application context.
*/
public WindowAndroid(Context context) {
this(context, DisplayAndroid.getNonMultiDisplay(context));
}
/**
* @param context The application context.
* @param display
*/
@SuppressLint("UseSparseArrays")
protected WindowAndroid(Context context, DisplayAndroid display) {
mApplicationContext = context.getApplicationContext();
// context does not have the same lifetime guarantees as an application context so we can't
// hold a strong reference to it.
mContextRef = new WeakReference<>(context);
mOutstandingIntents = new SparseArray<>();
mIntentErrors = new HashMap<>();
mVSyncMonitor = new VSyncMonitor(context, mVSyncListener);
mAccessibilityManager = (AccessibilityManager) mApplicationContext.getSystemService(
Context.ACCESSIBILITY_SERVICE);
mDisplayAndroid = display;
}
@CalledByNative
private static long createForTesting() {
WindowAndroid windowAndroid = new WindowAndroid(ContextUtils.getApplicationContext());
// |windowAndroid.getNativePointer()| creates native WindowAndroid object
// which stores a global ref to |windowAndroid|. Therefore |windowAndroid|
// is not immediately eligible for gc.
return windowAndroid.getNativePointer();
}
@CalledByNative
private void clearNativePointer() {
mNativeWindowAndroid = 0;
}
/**
* Set the delegate that will handle android permissions requests.
*/
@VisibleForTesting
public void setAndroidPermissionDelegate(AndroidPermissionDelegate delegate) {
mPermissionDelegate = delegate;
}
/**
* Shows an intent and returns the results to the callback object.
* @param intent The PendingIntent that needs to be shown.
* @param callback The object that will receive the results for the intent.
* @param errorId The ID of error string to be shown if activity is paused before intent
* results, or null if no message is required.
* @return Whether the intent was shown.
*/
public boolean showIntent(PendingIntent intent, IntentCallback callback, Integer errorId) {
return showCancelableIntent(intent, callback, errorId) >= 0;
}
/**
* Shows an intent and returns the results to the callback object.
* @param intent The intent that needs to be shown.
* @param callback The object that will receive the results for the intent.
* @param errorId The ID of error string to be shown if activity is paused before intent
* results, or null if no message is required.
* @return Whether the intent was shown.
*/
public boolean showIntent(Intent intent, IntentCallback callback, Integer errorId) {
return showCancelableIntent(intent, callback, errorId) >= 0;
}
/**
* Shows an intent that could be canceled and returns the results to the callback object.
* @param intent The PendingIntent that needs to be shown.
* @param callback The object that will receive the results for the intent.
* @param errorId The ID of error string to be shown if activity is paused before intent
* results, or null if no message is required.
* @return A non-negative request code that could be used for finishActivity, or
* START_INTENT_FAILURE if failed.
*/
public int showCancelableIntent(
PendingIntent intent, IntentCallback callback, Integer errorId) {
Log.d(TAG, "Can't show intent as context is not an Activity: " + intent);
return START_INTENT_FAILURE;
}
/**
* Shows an intent that could be canceled and returns the results to the callback object.
* @param intent The intent that needs to be shown.
* @param callback The object that will receive the results for the intent.
* @param errorId The ID of error string to be shown if activity is paused before intent
* results, or null if no message is required.
* @return A non-negative request code that could be used for finishActivity, or
* START_INTENT_FAILURE if failed.
*/
public int showCancelableIntent(Intent intent, IntentCallback callback, Integer errorId) {
Log.d(TAG, "Can't show intent as context is not an Activity: " + intent);
return START_INTENT_FAILURE;
}
/**
* Shows an intent that could be canceled and returns the results to the callback object.
* @param intentTrigger The callback that triggers the intent that needs to be shown. The value
* passed to the trigger is the request code used for issuing the intent.
* @param callback The object that will receive the results for the intent.
* @param errorId The ID of error string to be shown if activity is paused before intent
* results, or null if no message is required.
* @return A non-negative request code that could be used for finishActivity, or
* START_INTENT_FAILURE if failed.
*/
public int showCancelableIntent(Callback<Integer> intentTrigger, IntentCallback callback,
Integer errorId) {
Log.d(TAG, "Can't show intent as context is not an Activity");
return START_INTENT_FAILURE;
}
/**
* Force finish another activity that you had previously started with showCancelableIntent.
* @param requestCode The request code returned from showCancelableIntent.
*/
public void cancelIntent(int requestCode) {
Log.d(TAG, "Can't cancel intent as context is not an Activity: " + requestCode);
}
/**
* Removes a callback from the list of pending intents, so that nothing happens if/when the
* result for that intent is received.
* @param callback The object that should have received the results
* @return True if the callback was removed, false if it was not found.
*/
public boolean removeIntentCallback(IntentCallback callback) {
int requestCode = mOutstandingIntents.indexOfValue(callback);
if (requestCode < 0) return false;
mOutstandingIntents.remove(requestCode);
mIntentErrors.remove(requestCode);
return true;
}
/**
* Determine whether access to a particular permission is granted.
* @param permission The permission whose access is to be checked.
* @return Whether access to the permission is granted.
*/
@CalledByNative
public final boolean hasPermission(String permission) {
if (mPermissionDelegate != null) return mPermissionDelegate.hasPermission(permission);
return ApiCompatibilityUtils.checkPermission(
mApplicationContext, permission, Process.myPid(), Process.myUid())
== PackageManager.PERMISSION_GRANTED;
}
/**
* Determine whether the specified permission can be requested.
*
* <p>
* A permission can be requested in the following states:
* 1.) Default un-granted state, permission can be requested
* 2.) Permission previously requested but denied by the user, but the user did not select
* "Never ask again".
*
* @param permission The permission name.
* @return Whether the requesting the permission is allowed.
*/
@CalledByNative
public final boolean canRequestPermission(String permission) {
if (mPermissionDelegate != null) {
return mPermissionDelegate.canRequestPermission(permission);
}
Log.w(TAG, "Cannot determine the request permission state as the context "
+ "is not an Activity");
assert false : "Failed to determine the request permission state using a WindowAndroid "
+ "without an Activity";
return false;
}
/**
* Determine whether the specified permission is revoked by policy.
*
* @param permission The permission name.
* @return Whether the permission is revoked by policy and the user has no ability to change it.
*/
public final boolean isPermissionRevokedByPolicy(String permission) {
if (mPermissionDelegate != null) {
return mPermissionDelegate.isPermissionRevokedByPolicy(permission);
}
Log.w(TAG, "Cannot determine the policy permission state as the context "
+ "is not an Activity");
assert false : "Failed to determine the policy permission state using a WindowAndroid "
+ "without an Activity";
return false;
}
/**
* Requests the specified permissions are granted for further use.
* @param permissions The list of permissions to request access to.
* @param callback The callback to be notified whether the permissions were granted.
*/
public final void requestPermissions(String[] permissions, PermissionCallback callback) {
if (mPermissionDelegate != null) {
mPermissionDelegate.requestPermissions(permissions, callback);
return;
}
Log.w(TAG, "Cannot request permissions as the context is not an Activity");
assert false : "Failed to request permissions using a WindowAndroid without an Activity";
}
/**
* Displays an error message with a provided error message string.
* @param error The error message string to be displayed.
*/
public void showError(String error) {
if (error != null) {
Toast.makeText(mApplicationContext, error, Toast.LENGTH_SHORT).show();
}
}
/**
* Displays an error message from the given resource id.
* @param resId The error message string's resource id.
*/
public void showError(int resId) {
showError(mApplicationContext.getString(resId));
}
/**
* Displays an error message for a nonexistent callback.
* @param error The error message string to be displayed.
*/
protected void showCallbackNonExistentError(String error) {
showError(error);
}
/**
* Broadcasts the given intent to all interested BroadcastReceivers.
*/
public void sendBroadcast(Intent intent) {
mApplicationContext.sendBroadcast(intent);
}
/**
* @return DisplayAndroid instance belong to this window.
*/
public DisplayAndroid getDisplay() {
return mDisplayAndroid;
}
/**
* @return A reference to owning Activity. The returned WeakReference will never be null, but
* the contained Activity can be null (either if it has been garbage collected or if
* this is in the context of a WebView that was not created using an Activity).
*/
public WeakReference<Activity> getActivity() {
return new WeakReference<>(null);
}
/**
* @return The application context for this activity.
*/
public Context getApplicationContext() {
return mApplicationContext;
}
/**
* Saves the error messages that should be shown if any pending intents would return
* after the application has been put onPause.
* @param bundle The bundle to save the information in onPause
*/
public void saveInstanceState(Bundle bundle) {
bundle.putSerializable(WINDOW_CALLBACK_ERRORS, mIntentErrors);
}
/**
* Restores the error messages that should be shown if any pending intents would return
* after the application has been put onPause.
* @param bundle The bundle to restore the information from onResume
*/
public void restoreInstanceState(Bundle bundle) {
if (bundle == null) return;
Object errors = bundle.getSerializable(WINDOW_CALLBACK_ERRORS);
if (errors instanceof HashMap) {
@SuppressWarnings("unchecked")
HashMap<Integer, String> intentErrors = (HashMap<Integer, String>) errors;
mIntentErrors = intentErrors;
}
}
/**
* Notify any observers that the visibility of the Android Window associated
* with this Window has changed.
* @param visible whether the View is visible.
*/
public void onVisibilityChanged(boolean visible) {
if (mNativeWindowAndroid == 0) return;
nativeOnVisibilityChanged(mNativeWindowAndroid, visible);
}
/**
* For window instances associated with an activity, notifies any listeners
* that the activity has been stopped.
*/
protected void onActivityStopped() {
if (mNativeWindowAndroid == 0) return;
nativeOnActivityStopped(mNativeWindowAndroid);
}
/**
* For window instances associated with an activity, notifies any listeners
* that the activity has been started.
*/
protected void onActivityStarted() {
if (mNativeWindowAndroid == 0) return;
nativeOnActivityStarted(mNativeWindowAndroid);
}
@CalledByNative
private void requestVSyncUpdate() {
if (mVSyncPaused) {
mPendingVSyncRequest = true;
return;
}
mVSyncMonitor.requestUpdate();
}
/**
* An interface that intent callback objects have to implement.
*/
public interface IntentCallback {
/**
* Handles the data returned by the requested intent.
* @param window A window reference.
* @param resultCode Result code of the requested intent.
* @param data The data returned by the intent.
*/
void onIntentCompleted(WindowAndroid window, int resultCode, Intent data);
}
/**
* Callback for permission requests.
*/
public interface PermissionCallback {
/**
* Called upon completing a permission request.
* @param permissions The list of permissions in the result.
* @param grantResults Whether the permissions were granted.
*/
void onRequestPermissionsResult(String[] permissions, int[] grantResults);
}
/**
* Tests that an activity is available to handle the passed in intent.
* @param intent The intent to check.
* @return True if an activity is available to process this intent when started, meaning that
* Context.startActivity will not throw ActivityNotFoundException.
*/
public boolean canResolveActivity(Intent intent) {
return mApplicationContext.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
}
/**
* Destroys the c++ WindowAndroid object if one has been created.
*/
public void destroy() {
if (mNativeWindowAndroid != 0) {
// Native code clears |mNativeWindowAndroid|.
nativeDestroy(mNativeWindowAndroid);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mTouchExplorationMonitor != null) mTouchExplorationMonitor.destroy();
}
}
/**
* Returns a pointer to the c++ AndroidWindow object and calls the initializer if
* the object has not been previously initialized.
* @return A pointer to the c++ AndroidWindow.
*/
public long getNativePointer() {
if (mNativeWindowAndroid == 0) {
mNativeWindowAndroid = nativeInit(mDisplayAndroid.getDisplayId());
nativeSetVSyncPaused(mNativeWindowAndroid, mVSyncPaused);
}
return mNativeWindowAndroid;
}
/**
* Set the animation placeholder view, which we set to 'draw' during animations, such that
* animations aren't clipped by the SurfaceView 'hole'. This can be the SurfaceView itself
* or a view directly on top of it. This could be extended to many views if we ever need it.
*/
public void setAnimationPlaceholderView(View view) {
mAnimationPlaceholderView = view;
// The accessibility focus ring also gets clipped by the SurfaceView 'hole', so
// make sure the animation placeholder view is in place if touch exploration is on.
mIsTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
refreshWillNotDraw();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mTouchExplorationMonitor = new TouchExplorationMonitor();
}
}
/**
* Sets the keyboard accessory view.
* @param view This view sits at the bottom of the content area and pushes the content up rather
* than overlaying it. Currently used as a container for Autofill suggestions.
*/
public void setKeyboardAccessoryView(ViewGroup view) {
mKeyboardAccessoryView = view;
}
/**
* @see #setKeyboardAccessoryView(ViewGroup)
*/
public ViewGroup getKeyboardAccessoryView() {
return mKeyboardAccessoryView;
}
protected void registerKeyboardVisibilityCallbacks() {
}
protected void unregisterKeyboardVisibilityCallbacks() {
}
/**
* Adds a listener that is updated of keyboard visibility changes. This works as a best guess.
*
* @see org.chromium.ui.UiUtils#isKeyboardShowing(Context, View)
*/
public void addKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
if (mKeyboardVisibilityListeners.isEmpty()) {
registerKeyboardVisibilityCallbacks();
}
mKeyboardVisibilityListeners.add(listener);
}
/**
* @see #addKeyboardVisibilityListener(KeyboardVisibilityListener)
*/
public void removeKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
mKeyboardVisibilityListeners.remove(listener);
if (mKeyboardVisibilityListeners.isEmpty()) {
unregisterKeyboardVisibilityCallbacks();
}
}
/**
* Adds a listener that will be notified whenever a ContextMenu is closed.
*/
public void addContextMenuCloseListener(OnCloseContextMenuListener listener) {
mContextMenuCloseListeners.addObserver(listener);
}
/**
* Removes a listener from the list of listeners that will be notified when a
* ContextMenu is closed.
*/
public void removeContextMenuCloseListener(OnCloseContextMenuListener listener) {
mContextMenuCloseListeners.removeObserver(listener);
}
/**
* This hook is called whenever the context menu is being closed (either by
* the user canceling the menu with the back/menu button, or when an item is
* selected).
*/
public void onContextMenuClosed() {
for (OnCloseContextMenuListener listener : mContextMenuCloseListeners) {
listener.onContextMenuClosed();
}
}
/**
* To be called when the keyboard visibility state might have changed. Informs listeners of the
* state change IFF there actually was a change.
* @param isShowing The current (guesstimated) state of the keyboard.
*/
protected void keyboardVisibilityPossiblyChanged(boolean isShowing) {
if (mIsKeyboardShowing == isShowing) return;
mIsKeyboardShowing = isShowing;
// Clone the list in case a listener tries to remove itself during the callback.
LinkedList<KeyboardVisibilityListener> listeners =
new LinkedList<>(mKeyboardVisibilityListeners);
for (KeyboardVisibilityListener listener : listeners) {
listener.keyboardVisibilityChanged(isShowing);
}
}
/**
* Start a post-layout animation on top of web content.
*
* By default, Android optimizes what it shows on top of SurfaceViews (saves power).
* Effectively, layouts determine what gets drawn and post-layout animations outside
* of this area may be 'clipped'. Using this method to start such animations will
* ensure that nothing is clipped during the animation, and restore the optimal
* state when the animation ends.
*/
public void startAnimationOverContent(Animator animation) {
// We may not need an animation placeholder (eg. Webview doesn't use SurfaceView)
if (mAnimationPlaceholderView == null) return;
if (animation.isStarted()) throw new IllegalArgumentException("Already started.");
boolean added = mAnimationsOverContent.add(animation);
if (!added) throw new IllegalArgumentException("Already Added.");
// We start the animation in this method to help guarantee that we never get stuck in this
// state or leak objects into the set. Starting the animation here should guarantee that we
// get an onAnimationEnd callback, and remove this animation.
animation.start();
// When the first animation starts, make the placeholder 'draw' itself.
refreshWillNotDraw();
// When the last animation ends, remove the placeholder view,
// returning to the default optimized state.
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animation.removeListener(this);
mAnimationsOverContent.remove(animation);
refreshWillNotDraw();
}
});
}
/**
* Getter for the current context (not necessarily the application context).
* Make no assumptions regarding what type of Context is returned here, it could be for example
* an Activity or a Context created specifically to target an external display.
*/
public WeakReference<Context> getContext() {
// Return a new WeakReference to prevent clients from releasing our internal WeakReference.
return new WeakReference<>(mContextRef.get());
}
/**
* Return the current window token, or null.
*/
@CalledByNative
private IBinder getWindowToken() {
Activity activity = activityFromContext(mContextRef.get());
if (activity == null) return null;
Window window = activity.getWindow();
if (window == null) return null;
View decorView = window.peekDecorView();
if (decorView == null) return null;
return decorView.getWindowToken();
}
/**
* Update whether the placeholder is 'drawn' based on whether an animation is running
* or touch exploration is enabled - if either of those are true, we call
* setWillNotDraw(false) to ensure that the animation is drawn over the SurfaceView,
* and otherwise we call setWillNotDraw(true).
*/
private void refreshWillNotDraw() {
boolean willNotDraw = !mIsTouchExplorationEnabled && mAnimationsOverContent.isEmpty();
if (mAnimationPlaceholderView.willNotDraw() != willNotDraw) {
mAnimationPlaceholderView.setWillNotDraw(willNotDraw);
}
}
/**
* Pauses/Unpauses VSync. When VSync is paused the compositor for this window will idle, and
* requestAnimationFrame callbacks won't fire, etc.
*/
public void setVSyncPaused(boolean paused) {
if (mVSyncPaused == paused) return;
mVSyncPaused = paused;
if (!mVSyncPaused && mPendingVSyncRequest) requestVSyncUpdate();
if (mNativeWindowAndroid != 0) nativeSetVSyncPaused(mNativeWindowAndroid, paused);
}
private native long nativeInit(int displayId);
private native void nativeOnVSync(long nativeWindowAndroid,
long vsyncTimeMicros,
long vsyncPeriodMicros);
private native void nativeOnVisibilityChanged(long nativeWindowAndroid, boolean visible);
private native void nativeOnActivityStopped(long nativeWindowAndroid);
private native void nativeOnActivityStarted(long nativeWindowAndroid);
private native void nativeSetVSyncPaused(long nativeWindowAndroid, boolean paused);
private native void nativeDestroy(long nativeWindowAndroid);
}