diff --git a/app/build.gradle b/app/build.gradle index b3c143641..fad65a84a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,9 +105,6 @@ dependencies { implementation 'com.drakeet.drawer:drawer:1.0.3' implementation 'com.google.android.material:material:1.7.0-alpha02' implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3' - implementation 'com.github.seven332:streampipe:0.1.0' - implementation 'com.github.seven332:tuxiang:0.1.6' - implementation 'com.github.seven332:unifile:9ec57bcd8f' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0' diff --git a/app/src/main/java/com/hippo/streampipe/InputStreamPipe.java b/app/src/main/java/com/hippo/streampipe/InputStreamPipe.java new file mode 100644 index 000000000..c8fe14e01 --- /dev/null +++ b/app/src/main/java/com/hippo/streampipe/InputStreamPipe.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2016 Hippo Seven + * + * 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.hippo.streampipe; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A pipe to open {@code InputStream} + */ +public interface InputStreamPipe { + + /** + * Obtain the {@code InputStreamPipe}. + * Call it before opening {@code InputStream}. + */ + void obtain(); + + /** + * Release the {@code InputStreamPipe}. + */ + void release(); + + /** + * Open {@code InputStream} + * + * @return the {@code InputStream} + */ + @NonNull + InputStream open() throws IOException; + + /** + * Close the {@code InputStream} opened before. + */ + void close(); +} diff --git a/app/src/main/java/com/hippo/streampipe/OutputStreamPipe.java b/app/src/main/java/com/hippo/streampipe/OutputStreamPipe.java new file mode 100644 index 000000000..54f03d739 --- /dev/null +++ b/app/src/main/java/com/hippo/streampipe/OutputStreamPipe.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2016 Hippo Seven + * + * 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.hippo.streampipe; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A pipe to open {@code OutputStream} + */ +public interface OutputStreamPipe { + + /** + * Obtain the {@code OutputStreamPipe}. + * Call it before opening {@code OutputStream}. + */ + void obtain(); + + /** + * Release the {@code OutputStreamPipe}. + */ + void release(); + + /** + * Open {@code OutputStream} + * + * @return the {@code OutputStream} + */ + @NonNull + OutputStream open() throws IOException; + + /** + * Close the {@code OutputStream} opened before. + */ + void close(); +} diff --git a/app/src/main/java/com/hippo/tuxiang/BaseConfigChooser.java b/app/src/main/java/com/hippo/tuxiang/BaseConfigChooser.java new file mode 100644 index 000000000..2932657eb --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/BaseConfigChooser.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +// android-9.0.0_r30 + +public abstract class BaseConfigChooser implements EGLConfigChooser { + + private final int mEGLContextClientVersion; + + public BaseConfigChooser(int eglContextClientVersion, int[] configSpec) { + mEGLContextClientVersion = eglContextClientVersion; + mConfigSpec = filterConfigSpec(configSpec); + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + final int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + final int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + + final EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + final EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } + + public abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + + protected int[] mConfigSpec; + + private int[] filterConfigSpec(int[] configSpec) { + if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) { + return configSpec; + } + /* We know none of the subclasses define EGL_RENDERABLE_TYPE. + * And we know the configSpec is well formed. + */ + final int len = configSpec.length; + final int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); + newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + if (mEGLContextClientVersion == 2) { + newConfigSpec[len] = 0x0004; /* EGL_OPENGL_ES2_BIT */ + } else { + newConfigSpec[len] = 0x0040; /* EGL_OPENGL_ES3_BIT_KHR */ + } + newConfigSpec[len+1] = EGL10.EGL_NONE; + return newConfigSpec; + } +} diff --git a/app/src/main/java/com/hippo/tuxiang/ComponentSizeChooser.java b/app/src/main/java/com/hippo/tuxiang/ComponentSizeChooser.java new file mode 100644 index 000000000..d9487eac2 --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/ComponentSizeChooser.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +// android-9.0.0_r30 + +/** + * Choose a configuration with exactly the specified r,g,b,a sizes, + * and at least the specified depth and stencil sizes. + */ +public class ComponentSizeChooser extends BaseConfigChooser { + + public ComponentSizeChooser(int eglContextClientVersion, + int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(eglContextClientVersion, new int[] { + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for (final EGLConfig config : configs) { + final int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + final int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if ((d >= mDepthSize) && (s >= mStencilSize)) { + final int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + final int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + final int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + final int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + if ((r == mRedSize) && (g == mGreenSize) + && (b == mBlueSize) && (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + private final int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; +} diff --git a/app/src/main/java/com/hippo/tuxiang/DefaultContextFactory.java b/app/src/main/java/com/hippo/tuxiang/DefaultContextFactory.java new file mode 100644 index 000000000..621cc0c95 --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/DefaultContextFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import android.util.Log; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +// android-9.0.0_r30 + +public class DefaultContextFactory implements EGLContextFactory { + + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + + private final int mEGLContextClientVersion; + + public DefaultContextFactory(int eglContextClientVersion) { + mEGLContextClientVersion = eglContextClientVersion; + } + + @Override + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + final int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, + EGL10.EGL_NONE }; + + return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, + mEGLContextClientVersion != 0 ? attrib_list : null); + } + + @Override + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + if (!egl.eglDestroyContext(display, context)) { + Log.e("DefaultContextFactory", "display:" + display + " context: " + context); + if (GLStuff.LOG_THREADS) { + Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); + } + EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); + } + } +} diff --git a/app/src/main/java/com/hippo/tuxiang/DefaultWindowSurfaceFactory.java b/app/src/main/java/com/hippo/tuxiang/DefaultWindowSurfaceFactory.java new file mode 100644 index 000000000..817a00787 --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/DefaultWindowSurfaceFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import android.util.Log; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +// android-9.0.0_r30 + +public class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { + + @Override + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, + EGLConfig config, Object nativeWindow) { + EGLSurface result = null; + try { + result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); + } catch (IllegalArgumentException e) { + // This exception indicates that the surface flinger surface + // is not valid. This can happen if the surface flinger surface has + // been torn down, but the application has not yet been + // notified via SurfaceHolder.Callback.surfaceDestroyed. + // In theory the application should be notified first, + // but in practice sometimes it is not. See b/4588890 + Log.e("DefWinSurfaceFactory", "eglCreateWindowSurface", e); + } + return result; + } + + @Override + public void destroySurface(EGL10 egl, EGLDisplay display, + EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } +} diff --git a/app/src/main/java/com/hippo/tuxiang/EGLConfigChooser.java b/app/src/main/java/com/hippo/tuxiang/EGLConfigChooser.java new file mode 100644 index 000000000..5b7ce9827 --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/EGLConfigChooser.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +// android-9.0.0_r30 + +/** + * An interface for choosing an EGLConfig configuration from a list of + * potential configurations. + *
+ * This interface must be implemented by clients wishing to call + * {@link GLStuff#setEGLConfigChooser(EGLConfigChooser)} + */ +public interface EGLConfigChooser { + /** + * Choose a configuration from the list. Implementors typically + * implement this method by calling + * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. + * @param egl the EGL10 for the current display. + * @param display the current display. + * @return the chosen configuration. + */ + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); +} diff --git a/app/src/main/java/com/hippo/tuxiang/EGLContextFactory.java b/app/src/main/java/com/hippo/tuxiang/EGLContextFactory.java new file mode 100644 index 000000000..d05a574bf --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/EGLContextFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +/** + * An interface for customizing the eglCreateContext and eglDestroyContext calls. + */ +package com.hippo.tuxiang; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +// android-9.0.0_r30 + +/** + * An interface for customizing the eglCreateContext and eglDestroyContext calls. + *
+ * This interface must be implemented by clients wishing to call + * {@link GLStuff#setEGLContextFactory(EGLContextFactory)} + */ +public interface EGLContextFactory { + EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); + void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); +} diff --git a/app/src/main/java/com/hippo/tuxiang/EGLWindowSurfaceFactory.java b/app/src/main/java/com/hippo/tuxiang/EGLWindowSurfaceFactory.java new file mode 100644 index 000000000..c2a83dc4e --- /dev/null +++ b/app/src/main/java/com/hippo/tuxiang/EGLWindowSurfaceFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.hippo.tuxiang; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +// android-9.0.0_r30 + +/** + * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. + *
+ * This interface must be implemented by clients wishing to call
+ * {@link GLStuff#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
+ */
+public interface EGLWindowSurfaceFactory {
+ /**
+ * @return null if the surface cannot be constructed.
+ */
+ EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
+ Object nativeWindow);
+ void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
+}
diff --git a/app/src/main/java/com/hippo/tuxiang/EglHelper.java b/app/src/main/java/com/hippo/tuxiang/EglHelper.java
new file mode 100644
index 000000000..bab12e2fb
--- /dev/null
+++ b/app/src/main/java/com/hippo/tuxiang/EglHelper.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.hippo.tuxiang;
+
+import android.opengl.GLDebugHelper;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.io.Writer;
+import java.lang.ref.WeakReference;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+
+// android-9.0.0_r30
+
+/**
+ * An EGL helper class.
+ */
+class EglHelper {
+ public EglHelper(WeakReference
+ * Wrapping is typically used for debugging purposes.
+ *
+ * The default value is null.
+ * @param glWrapper the new GLWrapper
+ */
+ void setGLWrapper(GLWrapper glWrapper);
+
+ /**
+ * @return Returns the {@code GLWrapper}
+ */
+ GLWrapper getGLWrapper();
+
+ /**
+ * Set the debug flags to a new value. The value is
+ * constructed by OR-together zero or more
+ * of the DEBUG_CHECK_* constants. The debug flags take effect
+ * whenever a surface is created. The default value is zero.
+ * @param debugFlags the new debug flags
+ * @see #DEBUG_CHECK_GL_ERROR
+ * @see #DEBUG_LOG_GL_CALLS
+ */
+ void setDebugFlags(int debugFlags);
+
+ /**
+ * Get the current value of the debug flags.
+ * @return the current value of the debug flags.
+ */
+ int getDebugFlags();
+
+ /**
+ * Control whether the EGL context is preserved when the GLSurfaceView is paused and
+ * resumed.
+ *
+ * If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
+ *
+ * Prior to API level 11, whether the EGL context is actually preserved or not
+ * depends upon whether the Android device can support an arbitrary number of
+ * EGL contexts or not. Devices that can only support a limited number of EGL
+ * contexts must release the EGL context in order to allow multiple applications
+ * to share the GPU.
+ *
+ * If set to false, the EGL context will be released when the GLSurfaceView is paused,
+ * and recreated when the GLSurfaceView is resumed.
+ *
+ *
+ * The default is false.
+ *
+ * @param preserveOnPause preserve the EGL context when paused
+ */
+ void setPreserveEGLContextOnPause(boolean preserveOnPause);
+
+ /**
+ * @return true if the EGL context will be preserved when paused
+ */
+ boolean getPreserveEGLContextOnPause();
+
+ /**
+ * Set the renderer associated with this view. Also starts the thread that
+ * will call the renderer, which in turn causes the rendering to start.
+ * This method should be called once and only once in the life-cycle of
+ * a GLSurfaceView.
+ * The following GLSurfaceView methods can only be called before
+ * setRenderer is called:
+ *
+ * The following GLSurfaceView methods can only be called after
+ * setRenderer is called:
+ * If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ *
+ * If this method is not called, then by default
+ * a context will be created with no shared context and
+ * with a null attribute list.
+ */
+ void setEGLContextFactory(EGLContextFactory factory);
+
+ /**
+ * @return Returns the {@code EGLWindowSurfaceFactory}
+ */
+ EGLWindowSurfaceFactory getEGLWindowSurfaceFactory();
+
+ /**
+ * Install a custom EGLWindowSurfaceFactory.
+ * If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ *
+ * If this method is not called, then by default
+ * a window surface will be created with a null attribute list.
+ */
+ void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory);
+
+ /**
+ * @return Returns the {@code EGLContextFactory}
+ */
+ EGLContextFactory getEGLContextFactory();
+
+ /**
+ * Install a custom EGLConfigChooser.
+ * If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ *
+ * If no setEGLConfigChooser method is called, then by default the
+ * view will choose an EGLConfig that is compatible with the current
+ * android.view.Surface, with a depth buffer depth of
+ * at least 16 bits.
+ */
+ void setEGLConfigChooser(EGLConfigChooser configChooser);
+
+ /**
+ * @return Returns the {@code EGLConfigChooser}
+ */
+ EGLConfigChooser getEGLConfigChooser();
+
+ /**
+ * Inform the default EGLContextFactory and default EGLConfigChooser
+ * which EGLContext client version to pick.
+ * Use this method to create an OpenGL ES 2.0-compatible context.
+ * Example:
+ * Note: Activities which require OpenGL ES 2.0 should indicate this by
+ * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's
+ * AndroidManifest.xml file.
+ * If this method is called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * This method only affects the behavior of the default EGLContexFactory and the
+ * default EGLConfigChooser. If
+ * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied
+ * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.
+ * If
+ * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
+ * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
+ * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
+ */
+ void setEGLContextClientVersion(int version);
+
+ /**
+ * Get EGLContextClientVersion
+ */
+ int getEGLContextClientVersion();
+
+ /**
+ * Set the rendering mode. When renderMode is
+ * RENDERMODE_CONTINUOUSLY, the renderer is called
+ * repeatedly to re-render the scene. When renderMode
+ * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
+ * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
+ *
+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
+ * by allowing the GPU and CPU to idle when the view does not need to be updated.
+ *
+ * This method can only be called after {@link #setRenderer(Renderer)}
+ *
+ * @param renderMode one of the RENDERMODE_X constants
+ * @see #RENDERMODE_CONTINUOUSLY
+ * @see #RENDERMODE_WHEN_DIRTY
+ */
+ void setRenderMode(int renderMode);
+
+ /**
+ * Get the current rendering mode. May be called
+ * from any thread. Must not be called before a renderer has been set.
+ * @return the current rendering mode.
+ * @see #RENDERMODE_CONTINUOUSLY
+ * @see #RENDERMODE_WHEN_DIRTY
+ */
+ int getRenderMode();
+
+ /**
+ * Request that the renderer render a frame.
+ * This method is typically used when the render mode has been set to
+ * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
+ * May be called
+ * from any thread. Must not be called before a renderer has been set.
+ */
+ void requestRender();
+
+ /**
+ * Pause the rendering thread, optionally tearing down the EGL context
+ * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}.
+ *
+ * This method should be called when it is no longer desirable for the
+ * GLSurfaceView to continue rendering, such as in response to
+ * {@link android.app.Activity#onStop Activity.onStop}.
+ *
+ * Must not be called before a renderer has been set.
+ */
+ void onPause();
+
+ /**
+ * Resumes the rendering thread, re-creating the OpenGL context if necessary. It
+ * is the counterpart to {@link #onPause()}.
+ *
+ * This method should typically be called in
+ * {@link android.app.Activity#onStart Activity.onStart}.
+ *
+ * Must not be called before a renderer has been set.
+ */
+ void onResume();
+
+ /**
+ * Queue a runnable to be run on the GL rendering thread. This can be used
+ * to communicate with the Renderer on the rendering thread.
+ * Must not be called before a renderer has been set.
+ * @param r the runnable to be run on the GL rendering thread.
+ */
+ void queueEvent(Runnable r);
+}
diff --git a/app/src/main/java/com/hippo/tuxiang/GLSurfaceView.java b/app/src/main/java/com/hippo/tuxiang/GLSurfaceView.java
new file mode 100644
index 000000000..ffc5b6c38
--- /dev/null
+++ b/app/src/main/java/com/hippo/tuxiang/GLSurfaceView.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.hippo.tuxiang;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import java.lang.ref.WeakReference;
+
+// android-9.0.0_r30
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying OpenGL rendering.
+ *
+ * A GLSurfaceView provides the following features:
+ *
+ * For more information about how to use OpenGL, read the
+ * OpenGL developer guide.
+ * Typically you use GLSurfaceView by subclassing it and overriding one or more of the
+ * View system input event methods. If your application does not need to override event
+ * methods then GLSurfaceView can be used as-is. For the most part
+ * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing.
+ * For example, unlike a regular View, drawing is delegated to a separate Renderer object which
+ * is registered with the GLSurfaceView
+ * using the {@link #setRenderer(Renderer)} call.
+ *
+ *
+ *
+ *
+ * By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format,
+ * with at least a 16-bit depth buffer and no stencil.
+ *
+ * If you would prefer a different EGLConfig
+ * you can override the default behavior by calling one of the
+ * setEGLConfigChooser methods.
+ *
+ *
+ *
+ *
+ *
+ *
+ * To handle an event you will typically subclass GLSurfaceView and override the
+ * appropriate method, just as you would with any other View. However, when handling
+ * the event, you may need to communicate with the Renderer object
+ * that's running in the rendering thread. You can do this using any
+ * standard Java cross-thread communication mechanism. In addition,
+ * one relatively easy way to communicate with your renderer is
+ * to call
+ * {@link #queueEvent(Runnable)}. For example:
+ * Typically
+ * used for implementing debugging and tracing on top of the default
+ * GL interface. You would typically use this by creating your own class
+ * that implemented all the GL methods by delegating to another GL instance.
+ * Then you could add your own behavior before or after calling the
+ * delegate. All the GLWrapper would do was instantiate and return the
+ * wrapper GL instance:
+ *
+ * The renderer is responsible for making OpenGL calls to render a frame.
+ *
+ * GLSurfaceView clients typically create their own classes that implement
+ * this interface, and then call {@link GLStuff#setRenderer} to
+ * register the renderer with the GLSurfaceView.
+ *
+ *
+ * For more information about how to use OpenGL, read the
+ * OpenGL developer guide.
+ *
+ * Called when the rendering thread
+ * starts and whenever the EGL context is lost. The EGL context will typically
+ * be lost when the Android device awakes after going to sleep.
+ *
+ * Since this method is called at the beginning of rendering, as well as
+ * every time the EGL context is lost, this method is a convenient place to put
+ * code to create resources that need to be created when the rendering
+ * starts, and that need to be recreated when the EGL context is lost.
+ * Textures are an example of a resource that you might want to create
+ * here.
+ *
+ * Note that when the EGL context is lost, all OpenGL resources associated
+ * with that context will be automatically deleted. You do not need to call
+ * the corresponding "glDelete" methods such as glDeleteTextures to
+ * manually delete these lost resources.
+ *
+ * @param gl the GL interface. Use
+ * Called after the surface is created and whenever
+ * the OpenGL ES surface size changes.
+ *
+ * Typically you will set your viewport here. If your camera
+ * is fixed then you could also set your projection matrix here:
+ *
+ * This method is responsible for drawing the current frame.
+ *
+ * The implementation of this method typically looks like this:
+ *
+ * GL thread starts after {@link GLStuff#setRenderer(Renderer)} called, and
+ * after {@link android.view.View#onDetachedFromWindow} called except
+ * the first time.
+ *
+ * It is a good time to prepare resources.
+ */
+ void onGLThreadStart();
+
+ /**
+ * Called when the GL thread exits.
+ *
+ * GL thread ends after {@link android.view.View#onDetachedFromWindow} called.
+ *
+ * It is a good time to release resources.
+ */
+ void onGLThreadExit();
+
+ /**
+ * Called after {@link GLStuff#onPause()} called.
+ *
+ * It is a good time to stop animations.
+ */
+ void onGLThreadPause();
+
+ /**
+ * Called after {@link GLStuff#onResume()} called.
+ *
+ * It is a good time to restart animations.
+ */
+ void onGLThreadResume();
+}
diff --git a/app/src/main/java/com/hippo/tuxiang/SimpleEGLConfigChooser.java b/app/src/main/java/com/hippo/tuxiang/SimpleEGLConfigChooser.java
new file mode 100644
index 000000000..9a94281fa
--- /dev/null
+++ b/app/src/main/java/com/hippo/tuxiang/SimpleEGLConfigChooser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.hippo.tuxiang;
+
+// android-9.0.0_r30
+
+/**
+ * This class will choose a RGB_888 surface with
+ * or without a depth buffer.
+ */
+public class SimpleEGLConfigChooser extends ComponentSizeChooser {
+ public SimpleEGLConfigChooser(int eglContextClientVersion, boolean withDepthBuffer) {
+ super(eglContextClientVersion, 8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
+ }
+}
diff --git a/app/src/main/java/com/hippo/unifile/Contracts.java b/app/src/main/java/com/hippo/unifile/Contracts.java
new file mode 100644
index 000000000..8bed9fc7a
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/Contracts.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 Hippo Seven
+ *
+ * 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.hippo.unifile;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+final class Contracts {
+ private Contracts() {}
+
+ static String queryForString(Context context, Uri self, String column,
+ String defaultValue) {
+ final ContentResolver resolver = context.getContentResolver();
+
+ Cursor c = null;
+ try {
+ c = resolver.query(self, new String[] { column }, null, null, null);
+ if (c != null && c.moveToFirst() && !c.isNull(0)) {
+ return c.getString(0);
+ } else {
+ return defaultValue;
+ }
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ return defaultValue;
+ } finally {
+ Utils.closeQuietly(c);
+ }
+ }
+
+ static int queryForInt(Context context, Uri self, String column,
+ int defaultValue) {
+ return (int) queryForLong(context, self, column, defaultValue);
+ }
+
+ static long queryForLong(Context context, Uri self, String column,
+ long defaultValue) {
+ final ContentResolver resolver = context.getContentResolver();
+
+ Cursor c = null;
+ try {
+ c = resolver.query(self, new String[] { column }, null, null, null);
+ if (c != null && c.moveToFirst() && !c.isNull(0)) {
+ return c.getLong(0);
+ } else {
+ return defaultValue;
+ }
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ return defaultValue;
+ } finally {
+ Utils.closeQuietly(c);
+ }
+ }
+
+ @NonNull
+ static OutputStream openOutputStream(Context context, Uri uri) throws IOException {
+ try {
+ OutputStream os = context.getContentResolver().openOutputStream(uri);
+ if (os == null) {
+ throw new IOException("Can't open OutputStream");
+ }
+ return os;
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ throw new IOException("Can't open OutputStream", e);
+ }
+ }
+
+ @NonNull
+ static OutputStream openOutputStream(Context context, Uri uri, boolean append) throws IOException {
+ try {
+ OutputStream os = context.getContentResolver().openOutputStream(uri, append ? "wa" : "w");
+ if (os == null) {
+ throw new IOException("Can't open OutputStream");
+ }
+ return os;
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ throw new IOException("Can't open OutputStream", e);
+ }
+ }
+
+ @NonNull
+ static InputStream openInputStream(Context context, Uri uri) throws IOException {
+ try {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ if (is == null) {
+ throw new IOException("Can't open InputStream");
+ }
+ return is;
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ throw new IOException("Can't open InputStream", e);
+ }
+ }
+}
diff --git a/app/src/main/java/com/hippo/unifile/DocumentsContractApi19.java b/app/src/main/java/com/hippo/unifile/DocumentsContractApi19.java
new file mode 100644
index 000000000..f6d1b7e4b
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/DocumentsContractApi19.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.hippo.unifile;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.text.TextUtils;
+import android.util.Log;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+final class DocumentsContractApi19 {
+ private DocumentsContractApi19() {}
+
+ private static final String TAG = DocumentsContractApi19.class.getSimpleName();
+
+ public static boolean isDocumentUri(Context context, Uri self) {
+ return DocumentsContract.isDocumentUri(context, self);
+ }
+
+ public static String getName(Context context, Uri self) {
+ return Contracts.queryForString(context, self, DocumentsContract.Document.COLUMN_DISPLAY_NAME, null);
+ }
+
+ private static String getRawType(Context context, Uri self) {
+ return Contracts.queryForString(context, self, DocumentsContract.Document.COLUMN_MIME_TYPE, null);
+ }
+
+ public static String getType(Context context, Uri self) {
+ final String rawType = getRawType(context, self);
+ if (DocumentsContract.Document.MIME_TYPE_DIR.equals(rawType)) {
+ return null;
+ } else {
+ return rawType;
+ }
+ }
+
+ public static boolean isDirectory(Context context, Uri self) {
+ return DocumentsContract.Document.MIME_TYPE_DIR.equals(getRawType(context, self));
+ }
+
+ public static boolean isFile(Context context, Uri self) {
+ final String type = getRawType(context, self);
+ return !(DocumentsContract.Document.MIME_TYPE_DIR.equals(type) || TextUtils.isEmpty(type));
+ }
+
+ public static long lastModified(Context context, Uri self) {
+ return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_LAST_MODIFIED, -1L);
+ }
+
+ public static long length(Context context, Uri self) {
+ return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, -1L);
+ }
+
+ public static boolean canRead(Context context, Uri self) {
+ // Ignore if grant doesn't allow read
+ if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+
+ // Ignore documents without MIME
+ return !TextUtils.isEmpty(getRawType(context, self));
+ }
+
+ public static boolean canWrite(Context context, Uri self) {
+ // Ignore if grant doesn't allow write
+ if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+
+ final String type = getRawType(context, self);
+ final int flags = Contracts.queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0);
+
+ // Ignore documents without MIME
+ if (TextUtils.isEmpty(type)) {
+ return false;
+ }
+
+ // Deletable documents considered writable
+ if ((flags & DocumentsContract.Document.FLAG_SUPPORTS_DELETE) != 0) {
+ return true;
+ }
+
+ if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
+ && (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
+ // Directories that allow create considered writable
+ return true;
+ } else if (!TextUtils.isEmpty(type)
+ && (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0) {
+ // Writable normal files considered writable
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean delete(Context context, Uri self) {
+ try {
+ return DocumentsContract.deleteDocument(context.getContentResolver(), self);
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ // Maybe user ejects tf card
+ Log.e(TAG, "Failed to renameTo", e);
+ return false;
+ }
+ }
+
+ public static boolean exists(Context context, Uri self) {
+ final ContentResolver resolver = context.getContentResolver();
+
+ Cursor c = null;
+ try {
+ c = resolver.query(self, new String[] {
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
+ return null != c && c.getCount() > 0;
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ // Log.w(TAG, "Failed query: " + e);
+ return false;
+ } finally {
+ Utils.closeQuietly(c);
+ }
+ }
+}
diff --git a/app/src/main/java/com/hippo/unifile/DocumentsContractApi21.java b/app/src/main/java/com/hippo/unifile/DocumentsContractApi21.java
new file mode 100644
index 000000000..2b77a90a7
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/DocumentsContractApi21.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.hippo.unifile;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+final class DocumentsContractApi21 {
+ private DocumentsContractApi21() {}
+
+ private static final String TAG = DocumentsContractApi21.class.getSimpleName();
+
+ private static final String PATH_DOCUMENT = "document";
+ private static final String PATH_TREE = "tree";
+
+ public static Uri createFile(Context context, Uri self, String mimeType,
+ String displayName) {
+ try {
+ return DocumentsContract.createDocument(context.getContentResolver(), self, mimeType,
+ displayName);
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ // Maybe user ejects tf card
+ Log.e(TAG, "Failed to createFile: " + self, e);
+ return null;
+ }
+ }
+
+ public static Uri createDirectory(Context context, Uri self, String displayName) {
+ return createFile(context, self, DocumentsContract.Document.MIME_TYPE_DIR, displayName);
+ }
+
+ public static Uri prepareTreeUri(Uri treeUri) {
+ return DocumentsContract.buildDocumentUriUsingTree(treeUri,
+ DocumentsContract.getTreeDocumentId(treeUri));
+ }
+
+ public static String getTreeDocumentPath(Uri documentUri) {
+ final List
+ * The underlying {@link android.provider.DocumentsProvider} only defines a
+ * forward mapping from parent to child, so the reverse mapping of child to
+ * parent offered here is purely a convenience method, and it may be
+ * incorrect if the underlying tree structure changes.
+ *
+ * @return parent of the file, or null if it is the top of the file tree
+ */
+ @Nullable
+ public UniFile getParentFile() {
+ return mParent;
+ }
+
+ /**
+ * Indicates if this file represents a directory.
+ *
+ * @return {@code true} if this file is a directory, {@code false}
+ * otherwise.
+ * @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
+ */
+ public abstract boolean isDirectory();
+
+ /**
+ * Indicates if this file represents a file.
+ *
+ * @return {@code true} if this file is a file, {@code false} otherwise.
+ * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
+ */
+ public abstract boolean isFile();
+
+ /**
+ * Returns the time when this file was last modified, measured in
+ * milliseconds since January 1st, 1970, midnight. Returns -1 if the file
+ * does not exist, or if the modified time is unknown.
+ *
+ * @return the time when this file was last modified,
+ * Note that this method does not throw {@code IOException} on
+ * failure. Callers must check the return value.
+ *
+ * @return {@code true} if this file was deleted, {@code false} otherwise.
+ * @see android.provider.DocumentsContract#deleteDocument(ContentResolver,
+ * Uri)
+ */
+ public abstract boolean delete();
+
+ /**
+ * Returns a boolean indicating whether this file can be found.
+ *
+ * @return {@code true} if this file exists, {@code false} otherwise.
+ */
+ public abstract boolean exists();
+
+ /**
+ * Returns an array of files contained in the directory represented by this
+ * file.
+ *
+ * @return an array of files or {@code null}.
+ * @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri,
+ * String)
+ */
+ @Nullable
+ public abstract UniFile[] listFiles();
+
+ /**
+ * Gets a list of the files in the directory represented by this file. This
+ * list is then filtered through a FilenameFilter and the names of files
+ * with matching names are returned as an array of strings.
+ *
+ * @param filter the filter to match names against, may be {@code null}.
+ * @return an array of files or {@code null}.
+ */
+ @Nullable
+ public abstract UniFile[] listFiles(FilenameFilter filter);
+
+ /**
+ * Test there is a file with the display name in the directory.
+ *
+ * @return the file if found it, or {@code null}.
+ */
+ @Nullable
+ public abstract UniFile findFile(String displayName);
+
+ /**
+ * Renames this file to {@code displayName}.
+ *
+ * Note that this method does not throw {@code IOException} on
+ * failure. Callers must check the return value.
+ *
+ * Some providers may need to create a new file to reflect the rename,
+ * potentially with a different MIME type, so {@link #getUri()} and
+ * {@link #getType()} may change to reflect the rename.
+ *
+ * When renaming a directory, children previously enumerated through
+ * {@link #listFiles()} may no longer be valid.
+ *
+ * @param displayName the new display name.
+ * @return true on success.
+ * @see android.provider.DocumentsContract#renameDocument(ContentResolver,
+ * Uri, String)
+ */
+ public abstract boolean renameTo(String displayName);
+
+ /**
+ * Open a stream on to the content associated with the file, clean it if it exists
+ *
+ * @return the {@link OutputStream}
+ * @throws IOException
+ */
+ @NonNull
+ public abstract OutputStream openOutputStream() throws IOException;
+
+ /**
+ * Open a stream on to the content associated with the file
+ *
+ * @param append {@code true} for do not clean it if it exists
+ * @return the {@link OutputStream}
+ * @throws IOException
+ */
+ @NonNull
+ public abstract OutputStream openOutputStream(boolean append) throws IOException;
+
+ /**
+ * Open a stream on to the content associated with the file
+ *
+ * @return the {@link InputStream}
+ * @throws IOException
+ */
+ @NonNull
+ public abstract InputStream openInputStream() throws IOException;
+
+ /**
+ * Get a random access stuff of the UniFile
+ *
+ * @param mode "r" or "rw"
+ * @return the random access stuff
+ * @throws IOException
+ */
+ @NonNull
+ public abstract UniRandomAccessFile createRandomAccessFile(String mode) throws IOException;
+}
diff --git a/app/src/main/java/com/hippo/unifile/UniRandomAccessFile.java b/app/src/main/java/com/hippo/unifile/UniRandomAccessFile.java
new file mode 100644
index 000000000..4f31c57d1
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/UniRandomAccessFile.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016 Hippo Seven
+ *
+ * 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.hippo.unifile;
+
+/*
+ * Created by Hippo on 8/15/2016.
+ */
+
+import java.io.IOException;
+
+/**
+ * The UniRandomAccessFile is designed to emulate RandomAccessFile interface for UniFile
+ */
+public interface UniRandomAccessFile {
+
+ /**
+ * Closes this UniRandomAccessFile and releases any resources associated with it.
+ * A closed UniRandomAccessFile cannot perform any operations.
+ *
+ * @throws IOException
+ */
+ void close() throws IOException;
+
+ /**
+ * Returns the current offset in this file.
+ *
+ * @throws IOException
+ */
+ long getFilePointer() throws IOException;
+
+ /**
+ * Move current location pointer to the new offset.
+ *
+ * @param pos the offset position, measured in bytes from the beginning
+ * @throws IOException
+ */
+ void seek(long pos) throws IOException;
+
+ /**
+ * Attempts to skip over n bytes of input discarding the skipped bytes.
+ *
+ * @param n the number of bytes to be skipped
+ * @return the actual number of bytes skipped
+ * @throws IOException
+ */
+ int skipBytes(int n) throws IOException;
+
+ /**
+ * Returns the length of this file.
+ *
+ * @throws IOException
+ */
+ long length() throws IOException;
+
+ /**
+ * Sets the length of this file.
+ *
+ * @param newLength the desired length of the file
+ * @throws IOException
+ */
+ void setLength(long newLength) throws IOException;
+
+ /**
+ * Reads b.length bytes from this file into the byte array,
+ * starting at the current file pointer.
+ *
+ * @param b the buffer into which the data is read
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end of
+ * the file has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ int read(byte[] b) throws IOException;
+
+ /**
+ * Reads exactly len bytes from this file into the byte array,
+ * starting at the current file pointer.
+ *
+ * @param b the buffer into which the data is read
+ * @param off the start offset of the data
+ * @param len the number of bytes to read
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end of
+ * the file has been reached.
+ * @throws IOException
+ */
+ int read(byte[] b, int off, int len) throws IOException;
+
+ /**
+ * Writes b.length bytes from the specified byte array to this file,
+ * starting at the current file pointer.
+ *
+ * @param b the data
+ * @throws IOException
+ */
+ void write(byte[] b) throws IOException;
+
+ /**
+ * Writes len bytes from the specified byte array starting at offset off to this file.
+ *
+ * @param b the data
+ * @param off the start offset in the data
+ * @param len the number of bytes to write
+ * @throws IOException
+ */
+ void write(byte[] b, int off, int len) throws IOException;
+}
diff --git a/app/src/main/java/com/hippo/unifile/UriHandler.java b/app/src/main/java/com/hippo/unifile/UriHandler.java
new file mode 100644
index 000000000..8af47cbde
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/UriHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Hippo Seven
+ *
+ * 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.hippo.unifile;
+
+/*
+ * Created by Hippo on 8/16/2016.
+ */
+
+import android.content.Context;
+import android.net.Uri;
+
+/**
+ * A UriHandler is to get UniFile from custom uri for extensions
+ */
+public interface UriHandler {
+
+ /**
+ * Create a {@link UniFile} representing the uri
+ */
+ UniFile fromUri(Context context, Uri uri);
+}
diff --git a/app/src/main/java/com/hippo/unifile/UriOutputStream.java b/app/src/main/java/com/hippo/unifile/UriOutputStream.java
new file mode 100644
index 000000000..791954fc2
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/UriOutputStream.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Hippo Seven
+ *
+ * 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.hippo.unifile;
+
+/*
+ * Created by Hippo on 8/15/2016.
+ */
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+// The OutputStream from Context.getContentResolver().openOutputStream()
+// and FileProvider uri may throw Exception when write. The Exception looks like:
+// java.io.IOException: write failed: EBADF (Bad file descriptor)
+// But UriOutputStream can avoid it on my Nexus 5 cm13.
+// TODO need more test
+class UriOutputStream extends FileOutputStream {
+
+ private ParcelFileDescriptor mPfd;
+
+ private UriOutputStream(ParcelFileDescriptor pfd, FileDescriptor fd) {
+ super(fd);
+ mPfd = pfd;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mPfd.close();
+ super.close();
+ }
+
+ @NonNull
+ static OutputStream create(Context context, Uri uri, String mode) throws IOException {
+ ParcelFileDescriptor pfd;
+ try {
+ pfd = context.getContentResolver().openFileDescriptor(uri, mode);
+ } catch (Throwable e) {
+ Utils.throwIfFatal(e);
+ throw new IOException("Can't get ParcelFileDescriptor", e);
+ }
+ if (pfd == null) {
+ throw new IOException("Can't get ParcelFileDescriptor");
+ }
+ FileDescriptor fd = pfd.getFileDescriptor();
+ if (fd == null) {
+ throw new IOException("Can't get FileDescriptor");
+ }
+
+ return new UriOutputStream(pfd, fd);
+ }
+}
diff --git a/app/src/main/java/com/hippo/unifile/Utils.java b/app/src/main/java/com/hippo/unifile/Utils.java
new file mode 100644
index 000000000..8cdc1b224
--- /dev/null
+++ b/app/src/main/java/com/hippo/unifile/Utils.java
@@ -0,0 +1,41 @@
+package com.hippo.unifile;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+
+import java.io.Closeable;
+
+class Utils {
+
+ static void closeQuietly(Cursor c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Throwable e) {
+ throwIfFatal(e);
+ }
+ }
+ }
+
+ static void closeQuietly(Closeable is) {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (Throwable e) {
+ throwIfFatal(e);
+ }
+ }
+ }
+
+ static void throwIfFatal(@NonNull Throwable t) {
+ // values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495
+ if (t instanceof VirtualMachineError) {
+ throw (VirtualMachineError) t;
+ } else if (t instanceof ThreadDeath) {
+ throw (ThreadDeath) t;
+ } else if (t instanceof LinkageError) {
+ throw (LinkageError) t;
+ }
+ }
+}
+ *
+ *
+ *
+ *
+ * @param renderer the renderer to use to perform OpenGL drawing.
+ */
+ void setRenderer(Renderer renderer);
+
+ /**
+ * @return Returns the {@code Renderer}
+ */
+ Renderer getRenderer();
+
+ /**
+ * Install a custom EGLContextFactory.
+ *
+ * public MyView(Context context) {
+ * super(context);
+ * setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
+ * setRenderer(new MyRenderer());
+ * }
+ *
+ *
+ *
+ *
+ * Developer Guides
+ * Using GLSurfaceView
+ * Initializing GLSurfaceView
+ * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}.
+ * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or
+ * more of these methods before calling setRenderer:
+ *
+ *
+ * Specifying the android.view.Surface
+ * By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent
+ * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT).
+ * The exact format of a TRANSLUCENT surface is device dependent, but it will be
+ * a 32-bit-per-pixel surface with 8 bits per component.
+ * Choosing an EGL Configuration
+ * A given Android device may support multiple EGLConfig rendering configurations.
+ * The available configurations may differ in how many channels of data are present, as
+ * well as how many bits are allocated to each channel. Therefore, the first thing
+ * GLSurfaceView has to do when starting to render is choose what EGLConfig to use.
+ * Debug Behavior
+ * You can optionally modify the behavior of GLSurfaceView by calling
+ * one or more of the debugging methods {@link #setDebugFlags(int)},
+ * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but
+ * typically they are called before setRenderer so that they take effect immediately.
+ * Setting a Renderer
+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}.
+ * The renderer is
+ * responsible for doing the actual OpenGL rendering.
+ * Rendering Mode
+ * Once the renderer is set, you can control whether the renderer draws
+ * continuously or on-demand by calling
+ * {@link #setRenderMode}. The default is continuous rendering.
+ * Activity Life-cycle
+ * A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients
+ * are required to call {@link #onPause()} when the activity stops and
+ * {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to
+ * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
+ * the OpenGL display.
+ * Handling events
+ *
+ * class MyGLSurfaceView extends GLSurfaceView {
+ *
+ * private MyRenderer mMyRenderer;
+ *
+ * public void start() {
+ * mMyRenderer = ...;
+ * setRenderer(mMyRenderer);
+ * }
+ *
+ * public boolean onKeyDown(int keyCode, KeyEvent event) {
+ * if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ * queueEvent(new Runnable() {
+ * // This method will be called on the rendering
+ * // thread:
+ * public void run() {
+ * mMyRenderer.handleDpadCenter();
+ * }});
+ * return true;
+ * }
+ * return super.onKeyDown(keyCode, event);
+ * }
+ * }
+ *
+ *
+ */
+public class GLSurfaceView extends SurfaceView implements GLStuff, SurfaceHolder.Callback2 {
+ private final static String TAG = "GLSurfaceView";
+
+ /**
+ * Standard View constructor. In order to render something, you
+ * must call {@link #setRenderer} to register a renderer.
+ */
+ public GLSurfaceView(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Standard View constructor. In order to render something, you
+ * must call {@link #setRenderer} to register a renderer.
+ */
+ public GLSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGLThread != null) {
+ // GLThread may still be running if this view was never
+ // attached to a window.
+ mGLThread.requestExitAndWait();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void init() {
+ // Install a SurfaceHolder.Callback so we get notified when the
+ // underlying surface is created and destroyed
+ SurfaceHolder holder = getHolder();
+ holder.addCallback(this);
+ // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
+ // this statement if back-porting to 2.2 or older:
+ // holder.setFormat(PixelFormat.RGB_565);
+ //
+ // setType is not needed for SDK 2.0 or newer. Uncomment this
+ // statement if back-porting this code to older SDKs.
+ // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
+ }
+
+ @Override
+ public Object getNativeWindow() {
+ return getHolder();
+ }
+
+ @Override
+ public void setGLWrapper(GLWrapper glWrapper) {
+ mGLWrapper = glWrapper;
+ }
+
+ @Override
+ public GLWrapper getGLWrapper() {
+ return mGLWrapper;
+ }
+
+ @Override
+ public void setDebugFlags(int debugFlags) {
+ mDebugFlags = debugFlags;
+ }
+
+ @Override
+ public int getDebugFlags() {
+ return mDebugFlags;
+ }
+
+ @Override
+ public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
+ mPreserveEGLContextOnPause = preserveOnPause;
+ }
+
+ @Override
+ public boolean getPreserveEGLContextOnPause() {
+ return mPreserveEGLContextOnPause;
+ }
+
+ private void checkRenderThreadState() {
+ if (mGLThread != null) {
+ throw new IllegalStateException(
+ "setRenderer has already been called for this instance.");
+ }
+ }
+
+ @Override
+ public void setRenderer(Renderer renderer) {
+ checkRenderThreadState();
+ if (mEGLConfigChooser == null) {
+ mEGLConfigChooser = new SimpleEGLConfigChooser(mEGLContextClientVersion, true);
+ }
+ if (mEGLContextFactory == null) {
+ mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
+ }
+ if (mEGLWindowSurfaceFactory == null) {
+ mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
+ }
+ mRenderer = renderer;
+ mGLThread = new GLThread(mThisWeakRef);
+ mGLThread.start();
+ }
+
+ @Override
+ public Renderer getRenderer() {
+ return mRenderer;
+ }
+
+ @Override
+ public void setEGLContextFactory(EGLContextFactory factory) {
+ checkRenderThreadState();
+ mEGLContextFactory = factory;
+ }
+
+ @Override
+ public EGLContextFactory getEGLContextFactory() {
+ return mEGLContextFactory;
+ }
+
+ @Override
+ public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
+ checkRenderThreadState();
+ mEGLWindowSurfaceFactory = factory;
+ }
+
+ @Override
+ public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() {
+ return mEGLWindowSurfaceFactory;
+ }
+
+ @Override
+ public void setEGLConfigChooser(EGLConfigChooser configChooser) {
+ checkRenderThreadState();
+ mEGLConfigChooser = configChooser;
+ }
+
+ @Override
+ public EGLConfigChooser getEGLConfigChooser() {
+ return mEGLConfigChooser;
+ }
+
+ @Override
+ public void setEGLContextClientVersion(int version) {
+ checkRenderThreadState();
+ mEGLContextClientVersion = version;
+ }
+
+ @Override
+ public int getEGLContextClientVersion() {
+ return mEGLContextClientVersion;
+ }
+
+ @Override
+ public void setRenderMode(int renderMode) {
+ mGLThread.setRenderMode(renderMode);
+ }
+
+ @Override
+ public int getRenderMode() {
+ return mGLThread.getRenderMode();
+ }
+
+ @Override
+ public void requestRender() {
+ mGLThread.requestRender();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback2 interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mGLThread.surfaceCreated();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback2 interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // Surface will be destroyed when we return
+ mGLThread.surfaceDestroyed();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback2 interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ mGLThread.onWindowResize(w, h);
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback2 interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) {
+ if (mGLThread != null) {
+ mGLThread.requestRenderAndNotify(finishDrawing);
+ }
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback2 interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ if (mGLThread != null) {
+ mGLThread.requestRenderAndWait();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mGLThread.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ mGLThread.onResume();
+ }
+
+ @Override
+ public void queueEvent(Runnable r) {
+ mGLThread.queueEvent(r);
+ }
+
+ /**
+ * This method is used as part of the View class and is not normally
+ * called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (LOG_ATTACH_DETACH) {
+ Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
+ }
+ if (mDetached && (mRenderer != null)) {
+ int renderMode = RENDERMODE_CONTINUOUSLY;
+ if (mGLThread != null) {
+ renderMode = mGLThread.getRenderMode();
+ }
+ mGLThread = new GLThread(mThisWeakRef);
+ if (renderMode != RENDERMODE_CONTINUOUSLY) {
+ mGLThread.setRenderMode(renderMode);
+ }
+ mGLThread.start();
+ }
+ mDetached = false;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (LOG_ATTACH_DETACH) {
+ Log.d(TAG, "onDetachedFromWindow");
+ }
+ if (mGLThread != null) {
+ mGLThread.requestExitAndWait();
+ }
+ mDetached = true;
+ super.onDetachedFromWindow();
+ }
+
+ private final WeakReference
+ * class MyGLWrapper implements GLWrapper {
+ * GL wrap(GL gl) {
+ * return new MyGLImplementation(gl);
+ * }
+ * static class MyGLImplementation implements GL,GL10,GL11,... {
+ * ...
+ * }
+ * }
+ *
+ */
+public interface GLWrapper {
+ /**
+ * Wraps a gl interface in another gl interface.
+ * @param gl a GL interface that is to be wrapped.
+ * @return either the input argument or another GL object that wraps the input argument.
+ */
+ GL wrap(GL gl);
+}
diff --git a/app/src/main/java/com/hippo/tuxiang/Renderer.java b/app/src/main/java/com/hippo/tuxiang/Renderer.java
new file mode 100644
index 000000000..4c7239b0b
--- /dev/null
+++ b/app/src/main/java/com/hippo/tuxiang/Renderer.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.hippo.tuxiang;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+// android-9.0.0_r30
+
+/**
+ * A generic renderer interface.
+ * Developer Guides
+ * Threading
+ * The renderer will be called on a separate thread, so that rendering
+ * performance is decoupled from the UI thread. Clients typically need to
+ * communicate with the renderer from the UI thread, because that's where
+ * input events are received. Clients can communicate using any of the
+ * standard Java techniques for cross-thread communication, or they can
+ * use the {@link GLStuff#queueEvent(Runnable)} convenience method.
+ * EGL Context Lost
+ * There are situations where the EGL rendering context will be lost. This
+ * typically happens when device wakes up after going to sleep. When
+ * the EGL context is lost, all OpenGL resources (such as textures) that are
+ * associated with that context will be automatically deleted. In order to
+ * keep rendering correctly, a renderer must recreate any lost resources
+ * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method
+ * is a convenient place to do this.
+ *
+ * @see GLStuff#setRenderer(Renderer)
+ */
+public interface Renderer {
+ /**
+ * Called when the surface is created or recreated.
+ * instanceof to
+ * test if the interface supports GL11 or higher interfaces.
+ * @param config the EGLConfig of the created surface. Can be used
+ * to create matching pbuffers.
+ */
+ void onSurfaceCreated(GL10 gl, EGLConfig config);
+
+ /**
+ * Called when the surface changed size.
+ *
+ * void onSurfaceChanged(GL10 gl, int width, int height) {
+ * gl.glViewport(0, 0, width, height);
+ * // for a fixed camera, set the projection too
+ * float ratio = (float) width / height;
+ * gl.glMatrixMode(GL10.GL_PROJECTION);
+ * gl.glLoadIdentity();
+ * gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+ * }
+ *
+ * @param gl the GL interface. Use instanceof to
+ * test if the interface supports GL11 or higher interfaces.
+ */
+ void onSurfaceChanged(GL10 gl, int width, int height);
+
+ /**
+ * Called to draw the current frame.
+ *
+ * void onDrawFrame(GL10 gl) {
+ * gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+ * //... other gl calls to render the scene ...
+ * }
+ *
+ * @param gl the GL interface. Use instanceof to
+ * test if the interface supports GL11 or higher interfaces.
+ * @return {@code false} for not drew, will not swap buffers.
+ */
+ boolean onDrawFrame(GL10 gl);
+
+ /**
+ * Called when the GL thread exits.
+ * -1L if can't get it
+ * @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
+ */
+ public abstract long lastModified();
+
+ /**
+ * Returns the length of this file in bytes. Returns -1 if the file does not
+ * exist, or if the length is unknown. The result for a directory is not
+ * defined.
+ *
+ * @return the number of bytes in this file, -1L if can't get it
+ * @see android.provider.DocumentsContract.Document#COLUMN_SIZE
+ */
+ public abstract long length();
+
+ /**
+ * Indicates whether the current context is allowed to read from this file.
+ *
+ * @return {@code true} if this file can be read, {@code false} otherwise.
+ */
+ public abstract boolean canRead();
+
+ /**
+ * Indicates whether the current context is allowed to write to this file.
+ *
+ * @return {@code true} if this file can be written, {@code false}
+ * otherwise.
+ * @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
+ * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
+ * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
+ * @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE
+ */
+ public abstract boolean canWrite();
+
+ /**
+ * It works like mkdirs, but it will return true if the UniFile is directory
+ *
+ * @return {@code true} if the directory was created
+ * or if the directory already existed.
+ */
+ public abstract boolean ensureDir();
+
+ /**
+ * Make sure the UniFile is file
+ *
+ * @return {@code true} if the file can be created
+ * or if the file already existed.
+ */
+ public abstract boolean ensureFile();
+
+ /**
+ * Get child file of this directory, the child might not exist.
+ *
+ * @return the child file, {@code null} if not supported
+ */
+ @Nullable
+ public abstract UniFile subFile(String displayName);
+
+ /**
+ * Deletes this file.
+ *