11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, TeskaLabs
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CatVision.io SDK for Android
2 |
3 | CatVision.io SDK for Android is an library that enables to securely share screen of the app or device. It also enables to remotely control your app via mouse and keyboard events sent to your application.
4 |
5 | 
6 |
7 | You can watch a [demonstration video](https://www.youtube.com/watch?v=bKjMwUtapxc) from our early development stage.
8 |
9 | ## Read the docs
10 |
11 | Complete documentation for **CatVision.io** can be found at [https://docs.catvision.io](https://docs.catvision.io)
12 |
13 | In the docs you will also find out [how to integrate Catvision](https://docs.catvision.io) in your application.
14 |
15 | ## Build
16 |
17 | Clone this repo and cd into it
18 |
19 | ```
20 | $ git clone git@github.com:TeskaLabs/CatVision-Android.git
21 | $ cd CatVision-Android
22 | ```
23 |
24 | Clone submodules
25 |
26 | ```
27 | $ git submodule init
28 | $ git submodule update
29 | ```
30 |
31 | Now you can proceed with the following steps:
32 |
33 | 1. Build the VNC server library
34 | 2. Download SeaCat Client dependency
35 | 3. Build the cvio module's JNI
36 | 4. Build the AAR
37 |
38 | ### Build the VNC server library
39 |
40 | You need [Android NDK](https://developer.android.com/ndk/index.html) toolset to build the binaries for Android devices. They need to be available in your `$PATH` or `$ANDROID_NDK`.
41 |
42 | For example on Mac OSX, if you had installed Android NDK using the Android SDK manager before:
43 |
44 | ```
45 | $ export ANDROID_NDK="${HOME}/Library/Android/sdk/ndk-bundle"
46 | ```
47 |
48 | Then build the VNC server
49 |
50 | ```
51 | $ cd external
52 | $ ./build.sh
53 | ```
54 |
55 | ### Download SeaCat Client dependency
56 |
57 | The CatVision.io module depends on **SeaCat Client**. SeaCat takes care for identifying your device and making it able to connect securely.
58 |
59 | ```
60 | $ cp SeaCatClient_Android_v1611-rc-1-release.aar ./seacat
61 | ```
62 |
63 | ### Build the cvio module's JNI
64 |
65 | CatVision java class uses JNI interface to call the VNC server's functions.
66 |
67 | `$BASEDIR` is the path to the cloned repository.
68 |
69 | ```
70 | $ cd $BASEDIR/cvio/src/main/jni
71 | $ ./build.sh
72 | ```
73 |
74 | ### Build the project
75 |
76 | Use Android Studio: `Build->Clean Project`, `Build->Make Project`
77 |
78 | The **CatVision AAR** is now in `$BASEDIR/cvio/build/outputs/aar`
79 |
80 | TODO: build from command line
81 |
82 | ### Sign CatVision.IO AAR
83 |
84 | ```
85 | $ apksigner sign --ks truststore.ks --out catvision-io-sdk-android-v1801.aar --min-sdk-version 14 catvision-io-sdk-android-v1801-release-aligned.aar
86 | ```
87 |
88 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.fabric.io/public'
8 | }
9 | maven {
10 | url 'https://maven.google.com/'
11 | }
12 | google()
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:4.1.1'
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | classpath 'com.google.gms:google-services:4.3.4'
20 | classpath 'io.fabric.tools:gradle:1.25.4'
21 | }
22 | }
23 |
24 | allprojects {
25 | repositories {
26 | jcenter()
27 | maven { url 'https://jitpack.io' }
28 | maven {
29 | url 'https://maven.google.com/'
30 | }
31 | }
32 | }
33 |
34 | task clean(type: Delete) {
35 | delete rootProject.buildDir
36 | }
37 |
--------------------------------------------------------------------------------
/cvio/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/cvio/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | def getVersionCode = { ->
4 | try {
5 | def stdout = new ByteArrayOutputStream()
6 | exec {
7 | commandLine 'git', 'rev-list', '--first-parent', '--count', 'master'
8 | standardOutput = stdout
9 | }
10 | return Integer.parseInt(stdout.toString().trim())
11 | }
12 | catch (ignored) {
13 | return -1;
14 | }
15 | }
16 |
17 | def getVersionName = { ->
18 | try {
19 | def stdout = new ByteArrayOutputStream()
20 | exec {
21 | commandLine 'git', 'describe', '--abbrev=7', '--tag', '--dirty', '--always'
22 | standardOutput = stdout
23 | }
24 | return stdout.toString().trim()
25 | }
26 | catch (ignored) {
27 | return null;
28 | }
29 | }
30 |
31 | def getVersionNameDashes = { ->
32 | try {
33 | def vn = getVersionName()
34 | return vn.replace('.', '-')
35 | }
36 | catch (ignored) {
37 | return null;
38 | }
39 | }
40 |
41 | def getArchiveBaseName = { ->
42 | return "catvision-io-sdk-android-"+getVersionNameDashes()
43 | }
44 |
45 | android {
46 | compileSdkVersion 30
47 | buildToolsVersion '28.0.3'
48 |
49 | defaultConfig {
50 | minSdkVersion 14
51 | targetSdkVersion 30
52 | versionCode getVersionCode()
53 | versionName getVersionName()
54 | setProperty("archivesBaseName", getArchiveBaseName())
55 |
56 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
57 |
58 | }
59 | sourceSets {
60 | main {
61 | jniLibs.srcDir 'src/main/libs'
62 | jni.srcDirs = []
63 | }
64 | }
65 | buildTypes {
66 | release {
67 | minifyEnabled false
68 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
69 | }
70 | }
71 | }
72 |
73 | task assembleWithSeaCat(dependsOn: assemble) { doLast {
74 |
75 | android.libraryVariants.all { variant ->
76 | variant.outputs.each { output ->
77 | // Find SeaCat Client AAR
78 | def seacatAARFile = new File("${projectDir}/../seacat").list().find{it=~/SeaCat.*\.aar$/}
79 | if (seacatAARFile == null) {
80 | throw new GradleException('SeaCat AAR not found in project files.')
81 | }
82 | def seacatAARPath = "${projectDir}/../seacat/${seacatAARFile}"
83 |
84 | // Run merge script
85 | def process
86 | process = "${projectDir}/../merge_seacat_cvio_builds.sh ${seacatAARPath} ${output.outputFile.path}"
87 | process = process.execute()
88 | process.text.eachLine {println it}
89 | if (process.exitValue() != 0) {
90 | throw new GradleException("Execution of the merge script returned ${process.exitValue()}.")
91 | }
92 | println "$output.name\tassembled with SeaCat: ${seacatAARFile}"
93 | }
94 | }
95 | } }
96 |
97 | task releaseAndAlign(dependsOn: assembleWithSeaCat) { doLast {
98 | android.libraryVariants.all { variant ->
99 | if (variant.buildType.name == "release") {
100 | variant.outputs.each { output ->
101 |
102 | def env = System.getenv();
103 | def android_home = env.get('ANDROID_HOME');
104 | if (android_home == null) {
105 | println "Please set environment variable ADROID_HOME (export ANDROID_HOME=~/Library/Android/sdk/)."
106 | }
107 | def ZIPALIGN = new File(android_home, '/build-tools/28.0.3/zipalign').toString();
108 | def APKSIGNER = new File(android_home, '/build-tools/28.0.3/apksigner').toString();
109 |
110 | def outputPathNoExt = output.outputFile.path[0..(output.outputFile.path.lastIndexOf('.')-1)]
111 | def outputExt = output.outputFile.path[(output.outputFile.path.lastIndexOf('.')+1)..-1]
112 |
113 | // zipalign
114 | try {
115 | exec {
116 | executable "rm"
117 | args "${outputPathNoExt}-aligned.${outputExt}"
118 | }
119 | } catch (Exception e) {}
120 |
121 | def outputSignedPath = "${outputPathNoExt}-aligned.${outputExt}"
122 | exec {
123 | executable "${ZIPALIGN}"
124 | args '-v', '-p', '4', "${output.outputFile.path}", "${outputSignedPath}"
125 | }
126 | return
127 | // apksigner config
128 | // def pkcsConfigPath = '/tmp/pkcs11_java.cfg'
129 | // def pkcsConfigFile = new File(pkcsConfigPath)
130 | // try {
131 | // pkcsConfigFile.delete()
132 | // } catch(Exception e) {}
133 | // try {
134 | // pkcsConfigFile.createNewFile()
135 | // } catch (Exception e) {return null}
136 | // def newLine = System.getProperty("line.separator")
137 | // pkcsConfigFile << 'name = OpenSC-PKCS11' << newLine
138 | // pkcsConfigFile << 'description = SunPKCS11 via OpenSC' << newLine
139 | // pkcsConfigFile << 'library = /opt/local/lib/opensc-pkcs11.so' << newLine
140 | // pkcsConfigFile << 'slotListIndex = 0' << newLine
141 | // // run apksigner
142 | // exec {
143 | //// executable "${APKSIGNER}"
144 | //// args '--ks-provider-class' 'sun.security.pkcs11.SunPKCS11' '--ks-provider-arg' pkcsCopnfigPath '--ks' 'NONE' '--ks-type' 'PKCS11' "${outputSignedPath}"
145 | // executable "jarsigner"
146 | // args '-providerClass' 'sun.security.pkcs11.SunPKCS11' '-providerArg' '/tmp/pkcs11_java.cfg' '-keystore' 'NONE' '-storetype' 'PKCS11' ${outputSignedPath} //'"Certificate for Digital Signature"'
147 | // }
148 | }
149 | }
150 | }
151 | } }
152 |
153 | task sourcesJar(type: Jar) {
154 | from android.sourceSets.main.java.srcDirs
155 | classifier = 'sources'
156 | }
157 |
158 | dependencies {
159 | implementation fileTree(dir: 'libs', include: ['*.jar'])
160 | implementation 'androidx.appcompat:appcompat:1.2.0'
161 | implementation project(':seacat')
162 | implementation 'androidx.annotation:annotation:1.1.0'
163 | }
164 |
--------------------------------------------------------------------------------
/cvio/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mpavelka/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/cvio/src/androidTest/java/com/teskalabs/cvio/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.teskalabs.cvio.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/cvio/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/CVIOInitProvider.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.database.Cursor;
7 | import android.net.Uri;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 |
12 | /**
13 | * Created by ateska on 31.12.17.
14 | *
15 | * A lot easier initialization of CatVision.io SDK
16 | * Inspired by https://firebase.googleblog.com/2016/12/how-does-firebase-initialize-on-android.html
17 | * and https://medium.com/@andretietz/auto-initialize-your-android-library-2349daf06920
18 | *
19 | */
20 |
21 | final public class CVIOInitProvider extends ContentProvider {
22 |
23 | public CVIOInitProvider()
24 | {
25 | }
26 |
27 | @Override
28 | public boolean onCreate()
29 | {
30 | // get the context (Application context)
31 | Context context = getContext();
32 |
33 | return CatVision.init(context);
34 | }
35 |
36 | @Nullable
37 | @Override
38 | public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
39 | {
40 | return null;
41 | }
42 |
43 | @Nullable
44 | @Override
45 | public String getType(@NonNull Uri uri)
46 | {
47 | return null;
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public Uri insert(@NonNull Uri uri, ContentValues values)
53 | {
54 | return null;
55 | }
56 |
57 | @Override
58 | public int delete(@NonNull Uri uri, String selection, String[] selectionArgs)
59 | {
60 | return 0;
61 | }
62 |
63 | @Override
64 | public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs)
65 | {
66 | return 0;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/CVIOInternals.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import android.content.Intent;
4 | import com.teskalabs.seacat.android.client.SeaCatClient;
5 |
6 | class CVIOInternals {
7 |
8 | public static Intent createIntent(String action)
9 | {
10 | Intent Intent = new Intent(action);
11 | Intent.addCategory(CatVision.CATEGORY_CVIO);
12 | Intent.addFlags(android.content.Intent.FLAG_FROM_BACKGROUND);
13 | return Intent;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/CVIOSeaCatPlugin.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import com.teskalabs.seacat.android.client.SeaCatPlugin;
4 |
5 | import java.util.Properties;
6 |
7 | final public class CVIOSeaCatPlugin extends SeaCatPlugin {
8 |
9 | final private int port;
10 |
11 | CVIOSeaCatPlugin(int port)
12 | {
13 | this.port = port;
14 | }
15 |
16 | @Override
17 | public Properties getCharacteristics(){
18 | Properties p = new Properties();
19 | p.setProperty("RA", "vnc:"+port);
20 | return p;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/VNCDelegate.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | interface VNCDelegate
4 | {
5 | int takeImage(); // Return value: 0 - image has been pushed to VNCServer.push(), 1 - VNC server is requested to shutdown immediately
6 |
7 | // rfb* callbacks
8 |
9 | void rfbKbdAddEventProc(boolean down, long keySym, String client);
10 | void rfbKbdReleaseAllKeysProc(String client);
11 |
12 | /* Indicates either pointer movement or a pointer button press or release. The pointer is
13 | now at (x-position, y-position), and the current state of buttons 1 to 8 are represented
14 | by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed).
15 | On a conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and right
16 | buttons on the mouse. On a wheel mouse, each step of the wheel upwards is represented
17 | by a press and release of button 4, and each step downwards is represented by
18 | a press and release of button 5.
19 | From: http://www.vislab.usyd.edu.au/blogs/index.php/2009/05/22/an-headerless-indexed-protocol-for-input-1?blog=61
20 | */
21 | void rfbPtrAddEventProc(int buttonMask, int x, int y, String client);
22 |
23 | void rfbSetXCutTextProc(String text, String client);
24 |
25 | int rfbNewClientHook(String client);
26 | }
27 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/VNCServer.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.ContextWrapper;
6 | import android.graphics.PixelFormat;
7 | import android.media.Image;
8 | import android.os.Build;
9 | import android.util.Log;
10 |
11 | import com.teskalabs.seacat.android.client.SeaCatClient;
12 | import com.teskalabs.seacat.android.client.socket.SocketConfig;
13 |
14 | import java.io.IOException;
15 | import java.nio.ByteBuffer;
16 |
17 | import static com.teskalabs.cvio.cviojni.*;
18 |
19 | class VNCServer extends ContextWrapper {
20 |
21 | private static final String TAG = VNCServer.class.getName();
22 | protected static CVIOSeaCatPlugin cvioSeaCatPlugin = null;
23 |
24 | private Thread mVNCThread = null;
25 | private final String socketFileName;
26 | private static final int port = 5900;
27 |
28 | public VNCServer(Context base, VNCDelegate delegate) {
29 | super(base);
30 |
31 | // Enable SeaCat
32 | if (cvioSeaCatPlugin == null) {
33 | cvioSeaCatPlugin = new CVIOSeaCatPlugin(port);
34 | }
35 |
36 | jni_set_delegate(delegate);
37 |
38 | // There has to be one directory (/s/) - it is used to ensure correct access level
39 | socketFileName = getDir("cvio", Context.MODE_PRIVATE).getAbsolutePath() + "/s/vnc";
40 | }
41 |
42 | public void configureSeaCat() throws IOException {
43 | SeaCatClient.configureSocket(
44 | port,
45 | SocketConfig.Domain.AF_UNIX, SocketConfig.Type.SOCK_STREAM, 0,
46 | socketFileName, ""
47 | );
48 | }
49 |
50 |
51 | public boolean run(final int screenWidth, final int screenHeight)
52 | {
53 | if ((screenWidth < 0) || (screenHeight < 0))
54 | {
55 | Log.e(TAG, "Screen width/height is not specified");
56 | return false;
57 | }
58 |
59 | synchronized (this) {
60 | // Check if VNC Thread is alive
61 | while (this.mVNCThread != null) {
62 | int rc;
63 | rc = jni_shutdown();
64 | if (rc != 0) Log.w(TAG, "jni_shutdown returned: " + rc);
65 |
66 | try {
67 | this.mVNCThread.join(5000);
68 | if (this.mVNCThread.isAlive()) {
69 | Log.w(TAG, this.mVNCThread + " is still joining ...");
70 | continue;
71 | }
72 | this.mVNCThread = null;
73 | } catch (InterruptedException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 |
78 | // Prepare VNC thread and launch
79 | if (this.mVNCThread == null) {
80 | this.mVNCThread = new Thread(new Runnable() {
81 | public void run() {
82 | int rc;
83 |
84 | Log.d(TAG, "VNC Server started");
85 |
86 | rc = jni_run(socketFileName, screenWidth, screenHeight);
87 | if (rc != 0) Log.w(TAG, "VNC Server thread exited with rc: " + rc);
88 |
89 | Log.i(TAG, "VNC Server terminated");
90 | }
91 | });
92 | this.mVNCThread.setName("cvioVNCThread");
93 | this.mVNCThread.setDaemon(true);
94 | this.mVNCThread.start();
95 | }
96 | }
97 |
98 | return true;
99 | }
100 |
101 |
102 | public void shutdown() {
103 |
104 | if (this.mVNCThread == null) return;
105 |
106 | synchronized (this) {
107 |
108 | while (this.mVNCThread != null) {
109 | int rc;
110 | rc = jni_shutdown();
111 | if (rc != 0) Log.w(TAG, "jni_shutdown returned: " + rc);
112 |
113 | try {
114 | this.mVNCThread.join(5000);
115 | if (this.mVNCThread.isAlive()) {
116 | Log.w(TAG, this.mVNCThread + " is still joining ...");
117 | continue;
118 | }
119 | this.mVNCThread = null;
120 | } catch (InterruptedException e) {
121 | e.printStackTrace();
122 | }
123 | }
124 | }
125 | }
126 |
127 | // Signalize that we have an image ready
128 | public void imageReady() {
129 | jni_image_ready();
130 | }
131 |
132 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
133 | public void push(Image image, int pixelFormat) {
134 | Image.Plane[] planes = image.getPlanes();
135 | ByteBuffer b = planes[0].getBuffer();
136 | if (pixelFormat == PixelFormat.RGBA_8888) {
137 | // planes[0].getPixelStride() has to be 4 (32 bit)
138 | jni_push_pixels_rgba_8888(b, planes[0].getRowStride());
139 | }
140 | else if (pixelFormat == PixelFormat.RGB_565)
141 | {
142 | // planes[0].getPixelStride() has to be 16 (16 bit)
143 | jni_push_pixels_rgba_565(b, planes[0].getRowStride());
144 | }
145 | else
146 | {
147 | Log.e(TAG, "Image reader acquired unsupported image format " + pixelFormat);
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/cviojni.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | class cviojni {
6 |
7 | static {
8 | System.loadLibrary("cviojni");
9 | }
10 |
11 | static native int jni_run(String socketPath, int width, int height);
12 | static native int jni_shutdown();
13 |
14 | static native void jni_image_ready(); // Send a 'signal' to VNC server that we have a image ready
15 |
16 | static native int jni_push_pixels_rgba_8888(ByteBuffer pixels, int row_stride);
17 | static native int jni_push_pixels_rgba_565(ByteBuffer pixels, int row_stride);
18 |
19 | static native void jni_set_delegate(VNCDelegate ra);
20 | }
21 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/exceptions/CatVisionException.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio.exceptions;
2 |
3 | public class CatVisionException extends Exception {
4 | public CatVisionException() {}
5 | public CatVisionException(Exception e) {
6 | super(e);
7 | }
8 | public CatVisionException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/cvio/src/main/java/com/teskalabs/cvio/inapp/InAppInputManager.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio.inapp;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.os.SystemClock;
9 | import android.util.Log;
10 | import android.view.InputDevice;
11 | import android.view.KeyEvent;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 |
15 | import java.lang.ref.WeakReference;
16 | import java.lang.reflect.Field;
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.List;
20 |
21 | public class InAppInputManager implements Application.ActivityLifecycleCallbacks {
22 |
23 | private static final String TAG = InAppInputManager.class.getName();
24 |
25 | private final Object currentActivityLock = new Object(); // Synchronize access to currentActivity and currentRootView
26 | private WeakReference currentActivity = null;
27 |
28 | private boolean button1Pressed = false;
29 | private int metaState = 0; // State of the meta keys
30 |
31 | public InAppInputManager(Context context) {
32 | Application app = null;
33 | try {
34 | app = (Application) context;
35 | }
36 | catch (ClassCastException e)
37 | {
38 | app = null;
39 | }
40 |
41 | if (app == null)
42 | {
43 | Log.e(TAG, "Provided context is not an application - input events will not work");
44 | return;
45 | }
46 |
47 | app.registerActivityLifecycleCallbacks(this);
48 | }
49 |
50 | private View obtainTargetView() {
51 | final Activity a = obtainActivity();
52 | //Log.i(TAG, "obtainTargetView a:"+a);
53 | if (a == null) return null;
54 |
55 | View v = a.findViewById(android.R.id.content).getRootView();
56 | //Log.i(TAG, "obtainTargetView vr:"+a);
57 | if (v == null) return null;
58 | if (v.hasWindowFocus()) return v; // Quick way
59 |
60 | List lv = getWindowManagerViews();
61 | for(View vi : lv)
62 | {
63 | //Log.i(TAG, "obtainTargetView vi:"+vi+" f:"+vi.hasWindowFocus());
64 | if (vi.hasWindowFocus()) return vi;
65 | }
66 |
67 | return v;
68 | }
69 |
70 | private Activity obtainActivity() {
71 | synchronized (currentActivityLock) {
72 | if (currentActivity == null) return null;
73 | return currentActivity.get();
74 | }
75 | }
76 |
77 | //
78 |
79 | public void onMouseEvent(int buttonMask, int x, int y) {
80 |
81 | if ((!button1Pressed) && ((buttonMask & 1) != 0))
82 | {
83 | injectTouchEvent(1, MotionEvent.ACTION_DOWN, x, y);
84 | button1Pressed = true;
85 | }
86 |
87 | else if (button1Pressed)
88 | {
89 | if ((buttonMask & 1) == 0)
90 | {
91 | injectTouchEvent(1, MotionEvent.ACTION_UP, x, y);
92 | button1Pressed = false;
93 | }
94 |
95 | else injectTouchEvent(1, MotionEvent.ACTION_MOVE, x, y);
96 | }
97 | }
98 |
99 | private void injectTouchEvent(int buttonId, int event, int x, int y)
100 | {
101 | final View view = obtainTargetView();
102 | if (view == null) return;
103 | final Activity activity = obtainActivity();
104 | if (activity == null) return;
105 |
106 | int viewLocation[] = new int[2];
107 | view.getLocationOnScreen(viewLocation);
108 |
109 | MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties();
110 | pp.toolType = MotionEvent.TOOL_TYPE_FINGER;
111 | pp.id = 0;
112 | MotionEvent.PointerProperties[] pps = new MotionEvent.PointerProperties[]{pp};
113 |
114 | MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords();
115 | pc.size = 1;
116 | pc.pressure = 1;
117 | pc.x = x - viewLocation[0];
118 | pc.y = y - viewLocation[1];
119 | MotionEvent.PointerCoords[] pcs = new MotionEvent.PointerCoords[]{pc};
120 |
121 | long t = SystemClock.uptimeMillis();
122 |
123 | final MotionEvent e = MotionEvent.obtain(
124 | t, // long downTime
125 | t + 100, // long eventTime
126 | event, // int action
127 | pps.length, // int pointerCount
128 | pps, // MotionEvent.PointerProperties[] pointerProperties
129 | pcs, // MotionEvent.PointerCoords[] pointerCoords
130 | 0, // int metaState
131 | 0, // int buttonState
132 | 1, // float xPrecision
133 | 1, // float yPrecision
134 | 1, // int deviceId
135 | 0, // int edgeFlags
136 | InputDevice.SOURCE_TOUCHSCREEN, //int source
137 | 0 // int flags
138 | );
139 |
140 | activity.runOnUiThread(new Runnable() {
141 | public void run() {
142 |
143 | view.dispatchTouchEvent(e);
144 | }
145 | });
146 | }
147 |
148 | ///
149 |
150 | public void onKeyboardEvent(boolean down, KeySym ks)
151 | {
152 | if ((ks == KeySym.XK_Escape) && ((this.metaState & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON)) != 0))
153 | {
154 | if (!down) return;
155 | injectBackEvent();
156 | return;
157 | }
158 |
159 | if (ks.keyeventCode != -1)
160 | {
161 | injectKeyboardEvent(down, ks);
162 |
163 | if (ks.metaState != 0)
164 | {
165 | if (down) this.metaState |= ks.metaState;
166 | else this.metaState &= ~ks.metaState;
167 | }
168 |
169 | return;
170 | }
171 |
172 | Log.i(TAG, "onKeyboardEvent: down:"+down+" keySym:"+ks);
173 |
174 |
175 | }
176 |
177 | private void injectKeyboardEvent(boolean down, KeySym ks) {
178 | final View view = obtainTargetView();
179 | if (view == null) return;
180 | final Activity activity = obtainActivity();
181 | if (activity == null) return;
182 |
183 | long t = SystemClock.uptimeMillis();
184 |
185 | int action = KeyEvent.ACTION_UP;
186 | if (down) action = KeyEvent.ACTION_DOWN;
187 |
188 | final KeyEvent e = new KeyEvent(
189 | t, // downTime
190 | t + 100, // eventTime
191 | action, // action (ACTION_DOWN / ACTION_UP)
192 | ks.keyeventCode, // code
193 | 0, // Repeat
194 | this.metaState,
195 | 0, // Device Id
196 | 0 //ks.code
197 | );
198 |
199 | // Log.i(TAG, "Injecting:"+e);
200 |
201 | activity.runOnUiThread(new Runnable() {
202 | public void run() {
203 | view.dispatchKeyEvent(e);
204 | }
205 | });
206 | }
207 |
208 | ///
209 |
210 | private void injectBackEvent()
211 | {
212 | final Activity activity = obtainActivity();
213 | if (activity == null) return;
214 |
215 | activity.runOnUiThread(new Runnable() {
216 | public void run() {
217 | activity.onBackPressed();
218 | }
219 | });
220 | }
221 |
222 | ///
223 |
224 | @Override
225 | public void onActivityCreated(Activity activity, Bundle bundle) {
226 |
227 | }
228 |
229 | @Override
230 | public void onActivityStarted(Activity activity) {
231 | Log.i(TAG, "onActivityStarted:" + activity);
232 | synchronized (currentActivityLock) {
233 | this.currentActivity = new WeakReference<>(activity);
234 | }
235 | }
236 |
237 | @Override
238 | public void onActivityResumed(Activity activity) {
239 | Log.i(TAG, "onActivityResumed:" + activity);
240 | synchronized (currentActivityLock) {
241 | this.currentActivity = new WeakReference<>(activity);
242 | }
243 | }
244 |
245 | @Override
246 | public void onActivityPaused(Activity activity) {
247 | Log.i(TAG, "onActivityPaused:" + activity);
248 | synchronized (currentActivityLock) {
249 | if ((this.currentActivity != null) && (this.currentActivity.get() == activity)) {
250 | this.currentActivity = null;
251 | }
252 | }
253 | }
254 |
255 | @Override
256 | public void onActivityStopped(Activity activity) {
257 | Log.i(TAG, "onActivityStopped:" + activity);
258 | synchronized (currentActivityLock) {
259 | if ((this.currentActivity != null) && (this.currentActivity.get() == activity)) {
260 | this.currentActivity = null;
261 | }
262 | }
263 | }
264 |
265 | @Override
266 | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
267 | }
268 |
269 | @Override
270 | public void onActivityDestroyed(Activity activity) {
271 |
272 | }
273 |
274 | ///
275 |
276 | private static List getWindowManagerViews() {
277 | try {
278 |
279 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
280 | Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
281 |
282 | // get the list from WindowManagerImpl.mViews
283 | Class wmiClass = Class.forName("android.view.WindowManagerImpl");
284 | Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null);
285 |
286 | return viewsFromWM(wmiClass, wmiInstance);
287 |
288 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
289 |
290 | // get the list from WindowManagerGlobal.mViews
291 | Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
292 | Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null);
293 |
294 | return viewsFromWM(wmgClass, wmgInstance);
295 | }
296 |
297 | } catch (Exception e) {
298 | e.printStackTrace();
299 | }
300 |
301 | return new ArrayList();
302 | }
303 |
304 | private static List viewsFromWM(Class wmClass, Object wmInstance) throws Exception {
305 |
306 | Field viewsField = wmClass.getDeclaredField("mViews");
307 | viewsField.setAccessible(true);
308 | Object views = viewsField.get(wmInstance);
309 |
310 | if (views instanceof List) {
311 | return (List) viewsField.get(wmInstance);
312 | } else if (views instanceof View[]) {
313 | return Arrays.asList((View[])viewsField.get(wmInstance));
314 | }
315 |
316 | return new ArrayList();
317 | }
318 |
319 | }
320 |
--------------------------------------------------------------------------------
/cvio/src/main/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 | PROJECT_PATH:= $(LOCAL_PATH)/../../../..
3 |
4 |
5 | include $(CLEAR_VARS)
6 | LOCAL_MODULE := libvncserver
7 | LOCAL_SRC_FILES := $(PROJECT_PATH)/external/lib/$(TARGET_ARCH_ABI)/libvncserver.a
8 | include $(PREBUILT_STATIC_LIBRARY)
9 |
10 |
11 | include $(CLEAR_VARS)
12 | LOCAL_MODULE := libjpeg_static
13 | LOCAL_SRC_FILES := $(PROJECT_PATH)/external/lib/$(TARGET_ARCH_ABI)/libjpeg_static.a
14 | include $(PREBUILT_STATIC_LIBRARY)
15 |
16 |
17 | include $(CLEAR_VARS)
18 | LOCAL_MODULE := libpng
19 | LOCAL_SRC_FILES := $(PROJECT_PATH)/external/lib/$(TARGET_ARCH_ABI)/libpng.a
20 | include $(PREBUILT_STATIC_LIBRARY)
21 |
22 |
23 | include $(CLEAR_VARS)
24 |
25 | LOCAL_SRC_FILES:= \
26 | cviojni.c
27 |
28 | WITH_WEBSOCKETS:=0
29 |
30 | LOCAL_C_INCLUDES := $(PROJECT_PATH)/external/include
31 |
32 |
33 | LOCAL_LDLIBS:=-llog -lz
34 | LOCAL_STATIC_LIBRARIES := libvncserver libjpeg_static libpng
35 | LOCAL_MODULE:= cviojni
36 |
37 |
38 | include $(BUILD_SHARED_LIBRARY)
39 |
--------------------------------------------------------------------------------
/cvio/src/main/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
2 | APP_PLATFORM := android-14
3 |
4 |
--------------------------------------------------------------------------------
/cvio/src/main/jni/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | JAVAC=javac
4 |
5 | # # Clean everything first
6 | rm -f com_teskalabs_cvio_android_cviojni.h
7 | rm -fr ../obj ../libs/armeabi-v7a ../libs/armeabi ../libs/x86
8 |
9 | # Prepare compiled class
10 | mkdir -p ../../../build/jni/classes
11 | ${JAVAC} -d ../../../build/jni/classes -classpath ~/Library/Android/sdk/platforms/android-21/android.jar:../java ../java/com/teskalabs/cvio/cviojni.java
12 |
13 | # Prepare header file
14 | javah -d . -classpath ~/Library/Android/sdk/platforms/android-21/android.jar:../../../build/jni/classes com.teskalabs.cvio.cviojni
15 |
16 | # Compile Android JNI
17 | ${ANDROID_NDK}/ndk-build -B
18 |
--------------------------------------------------------------------------------
/cvio/src/main/jni/com_teskalabs_cvio_cviojni.h:
--------------------------------------------------------------------------------
1 | /* DO NOT EDIT THIS FILE - it is machine generated */
2 | #include
3 | /* Header for class com_teskalabs_cvio_cviojni */
4 |
5 | #ifndef _Included_com_teskalabs_cvio_cviojni
6 | #define _Included_com_teskalabs_cvio_cviojni
7 | #ifdef __cplusplus
8 | extern "C" {
9 | #endif
10 | /*
11 | * Class: com_teskalabs_cvio_cviojni
12 | * Method: jni_run
13 | * Signature: (Ljava/lang/String;II)I
14 | */
15 | JNIEXPORT jint JNICALL Java_com_teskalabs_cvio_cviojni_jni_1run
16 | (JNIEnv *, jclass, jstring, jint, jint);
17 |
18 | /*
19 | * Class: com_teskalabs_cvio_cviojni
20 | * Method: jni_shutdown
21 | * Signature: ()I
22 | */
23 | JNIEXPORT jint JNICALL Java_com_teskalabs_cvio_cviojni_jni_1shutdown
24 | (JNIEnv *, jclass);
25 |
26 | /*
27 | * Class: com_teskalabs_cvio_cviojni
28 | * Method: jni_image_ready
29 | * Signature: ()V
30 | */
31 | JNIEXPORT void JNICALL Java_com_teskalabs_cvio_cviojni_jni_1image_1ready
32 | (JNIEnv *, jclass);
33 |
34 | /*
35 | * Class: com_teskalabs_cvio_cviojni
36 | * Method: jni_push_pixels_rgba_8888
37 | * Signature: (Ljava/nio/ByteBuffer;I)I
38 | */
39 | JNIEXPORT jint JNICALL Java_com_teskalabs_cvio_cviojni_jni_1push_1pixels_1rgba_18888
40 | (JNIEnv *, jclass, jobject, jint);
41 |
42 | /*
43 | * Class: com_teskalabs_cvio_cviojni
44 | * Method: jni_push_pixels_rgba_565
45 | * Signature: (Ljava/nio/ByteBuffer;I)I
46 | */
47 | JNIEXPORT jint JNICALL Java_com_teskalabs_cvio_cviojni_jni_1push_1pixels_1rgba_1565
48 | (JNIEnv *, jclass, jobject, jint);
49 |
50 | /*
51 | * Class: com_teskalabs_cvio_cviojni
52 | * Method: jni_set_delegate
53 | * Signature: (Lcom/teskalabs/cvio/VNCDelegate;)V
54 | */
55 | JNIEXPORT void JNICALL Java_com_teskalabs_cvio_cviojni_jni_1set_1delegate
56 | (JNIEnv *, jclass, jobject);
57 |
58 | #ifdef __cplusplus
59 | }
60 | #endif
61 | #endif
62 |
--------------------------------------------------------------------------------
/cvio/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CatVision.io
3 |
4 |
--------------------------------------------------------------------------------
/cvio/src/test/java/com/teskalabs/cvio/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.teskalabs.cvio;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'io.fabric'
3 |
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion '28.0.3'
7 | defaultConfig {
8 | applicationId "io.catvision.appl"
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 | versionCode 20120403
12 | versionName 'v201204-3'
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | sourceSets.main {
22 | jniLibs.srcDir 'src/main/libs'
23 | jni.srcDirs = [] //disable automatic ndk-build call
24 | }
25 | productFlavors {
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar'])
31 | implementation project(':seacat')
32 | implementation project(':cvio')
33 |
34 | implementation 'com.github.apl-devs:appintro:v4.2.2'
35 | implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.21'
36 | implementation 'me.dm7.barcodescanner:zxing:1.9.8'
37 | implementation 'androidx.appcompat:appcompat:1.2.0'
38 | implementation 'com.android.support.constraint:constraint-layout:2.0.4'
39 | // Crashlytics
40 | implementation 'com.google.firebase:firebase-core:18.0.0'
41 | implementation 'com.google.firebase:firebase-crashlytics-ktx:17.3.0'
42 | implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0'
43 |
44 | implementation 'com.google.firebase:firebase-core:18.0.0'
45 | // implementation 'com.android.support:support-v4:26.+'
46 | implementation 'androidx.appcompat:appcompat:1.2.0'
47 | implementation 'com.google.android.material:material:1.2.1'
48 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
49 | }
50 |
51 | apply plugin: 'com.google.gms.google-services'
52 |
--------------------------------------------------------------------------------
/demo/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "500594913840",
4 | "firebase_url": "https://catvision-io.firebaseio.com",
5 | "project_id": "catvision-io",
6 | "storage_bucket": "catvision-io.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:500594913840:android:e62712fcb81de0fc",
12 | "android_client_info": {
13 | "package_name": "io.catvision.app"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "500594913840-53j3d26oj6545a2jtok11l4pn2eimo4l.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyA30dAiJyft9T7tzhnvTDkC2D21ewn0GZM"
25 | }
26 | ],
27 | "services": {
28 | "analytics_service": {
29 | "status": 1
30 | },
31 | "appinvite_service": {
32 | "status": 1,
33 | "other_platform_oauth_client": []
34 | },
35 | "ads_service": {
36 | "status": 2
37 | }
38 | }
39 | },
40 | {
41 | "client_info": {
42 | "mobilesdk_app_id": "1:500594913840:android:ed34010ab865b2a4",
43 | "android_client_info": {
44 | "package_name": "io.catvision.appl"
45 | }
46 | },
47 | "oauth_client": [
48 | {
49 | "client_id": "500594913840-53j3d26oj6545a2jtok11l4pn2eimo4l.apps.googleusercontent.com",
50 | "client_type": 3
51 | }
52 | ],
53 | "api_key": [
54 | {
55 | "current_key": "AIzaSyA30dAiJyft9T7tzhnvTDkC2D21ewn0GZM"
56 | }
57 | ],
58 | "services": {
59 | "analytics_service": {
60 | "status": 1
61 | },
62 | "appinvite_service": {
63 | "status": 1,
64 | "other_platform_oauth_client": []
65 | },
66 | "ads_service": {
67 | "status": 2
68 | }
69 | }
70 | }
71 | ],
72 | "configuration_version": "1"
73 | }
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mpavelka/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
46 |
47 |
51 |
52 |
56 |
57 |
61 |
62 |
66 |
67 |
71 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/demo/src/main/assets/about/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 | CatVision.io Try Me! App is an application created for demonstration of what CatVision.io SDK for Android can do.
16 |
17 |
18 | CatVision.io SDK for Android provides an easy and secure remote access to a screen of your mobile application. Purposely built for customer and tech support. IT service desk agents and customer tech support only need a web browser to remotely resolve any issues. It allows you to quickly solve any user issues with mobile apps by seeing what customer sees and guide him interactively and quickly.
19 |
20 |
21 | Contact us at team@catvision.io and get started right away!
22 |
17 | For screen share of this application and your device, you need to first Pair CatVision.io mobile app with Web-App located on app.catvision.io. You can do this by scanning QR Code placed in Web-App or via Browser on your mobile, just tap on PAIR ME, then via Browser button, sign up or log in and then tap on the proper button. Refresh page and your app is paired with Web-App and you can start screen-sharing by tapping on SHARE ME button.
18 |
19 |
20 |
21 |
Screen Share
22 |
23 | Screen-sharing is beneficial technology; you can see from your browser what is displayed on the screen of your mobile device, and more! You can even control UI elements of the application itself. Understandably, you can't control device itself. It would need to be approved by the device manufacturer, for example, Samsung or Huawei. But you can see everything that's going on!
24 |
25 |
26 |
27 |
Unpair
28 |
29 | Function unpair serves its logical purpose; it will delete your pairing with Web-App, for example, if you wish to pair application with new Product inside Web-App.
30 |
31 |
32 |
33 |
What is "Product"?
34 |
35 | The product is how we call environment from where you can access mobile devices with your application equipped with CatVision.io SDK. Every application has its Product, so it is more clear and well-arranged that way. You can have one Product or ten Products, and it depends only on you.
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeskaLabs/CatVision-io-SDK-Android/158b6cdabb2544a2ca830e360cd737b2f0dfc2ff/demo/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/demo/src/main/ic_launcher_catvision-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeskaLabs/CatVision-io-SDK-Android/158b6cdabb2544a2ca830e360cd737b2f0dfc2ff/demo/src/main/ic_launcher_catvision-web.png
--------------------------------------------------------------------------------
/demo/src/main/ic_launcher_round-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeskaLabs/CatVision-io-SDK-Android/158b6cdabb2544a2ca830e360cd737b2f0dfc2ff/demo/src/main/ic_launcher_round-web.png
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.webkit.WebSettings;
7 | import android.webkit.WebView;
8 | import android.webkit.WebViewClient;
9 |
10 | import androidx.appcompat.app.AppCompatActivity;
11 | import androidx.appcompat.widget.Toolbar;
12 |
13 | public class AboutActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_about);
19 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
20 | setSupportActionBar(toolbar);
21 |
22 | try {
23 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
24 | getSupportActionBar().setDisplayShowHomeEnabled(true);
25 | } catch (NullPointerException e) {
26 | e.printStackTrace();
27 | }
28 |
29 | // Web view
30 | WebView webView = (WebView)findViewById(R.id.mainWebView);
31 | WebSettings webSettings = webView.getSettings();
32 | webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
33 | // other
34 | webSettings.setJavaScriptEnabled(true);
35 | webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
36 | webSettings.setAllowFileAccessFromFileURLs(true);
37 | webSettings.setAllowUniversalAccessFromFileURLs(true);
38 | // important!
39 | webView.setWebViewClient(new MyViewClient());
40 | webView.loadUrl("file:///android_asset/about/index.html");
41 | }
42 |
43 | @Override
44 | public boolean onSupportNavigateUp() {
45 | onBackPressed();
46 | return true;
47 | }
48 |
49 | private class MyViewClient extends WebViewClient {
50 | @Override
51 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
52 | if(url.startsWith("mailto:")){
53 | Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
54 | startActivity(i);
55 | }
56 | else{
57 | view.loadUrl(url);
58 | }
59 | return true;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/ApiKeyObtainerActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.net.Uri;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.text.InputType;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.widget.EditText;
15 |
16 | import androidx.annotation.NonNull;
17 | import androidx.appcompat.app.AppCompatActivity;
18 | import androidx.appcompat.widget.Toolbar;
19 | import androidx.core.app.ActivityCompat;
20 |
21 | import com.google.firebase.analytics.FirebaseAnalytics;
22 |
23 | public class ApiKeyObtainerActivity extends AppCompatActivity {
24 | // Permissions
25 | private static int CAMERA_PERMISSION = 201;
26 | // Requests
27 | private static int QR_CODE_REQUEST = 1;
28 |
29 | private FirebaseAnalytics mFirebaseAnalytics;
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_api_key_obtainer);
35 | Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
36 | setSupportActionBar(toolbar);
37 |
38 | try {
39 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
40 | getSupportActionBar().setDisplayShowHomeEnabled(true);
41 | } catch (NullPointerException e) {
42 | e.printStackTrace();
43 | }
44 |
45 | mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
46 | }
47 |
48 | @Override
49 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
50 | super.onActivityResult(requestCode, resultCode, data);
51 | if (requestCode == QR_CODE_REQUEST) {
52 | if (resultCode == RESULT_OK) {
53 | setResult(resultCode, data);
54 | finish();
55 | }
56 | }
57 | }
58 |
59 | public void onClickQRCodeScan(View v) {
60 | if (isCameraPermissionGranted())
61 | startQRScanActivity();
62 | }
63 |
64 | public void onClickGetDeepLink(View v) {
65 | mFirebaseAnalytics.logEvent(getResources().getString(R.string.event_pair_link), new Bundle());
66 | openURLinWebBrowser(getResources().getString(R.string.open_app_with_key_url));
67 | finish();
68 | }
69 |
70 | // Custom methods ------------------------------------------------------------------------------
71 | /**
72 | * Starts an activity that retrieves the QR code.
73 | */
74 | public void startQRScanActivity() {
75 | mFirebaseAnalytics.logEvent(getResources().getString(R.string.event_pair_qr), new Bundle());
76 | Intent intent = new Intent(getApplicationContext(), QRCodeScannerActivity.class);
77 | startActivityForResult(intent, QR_CODE_REQUEST);
78 | }
79 |
80 | /**
81 | * Calls an intent to open a specified URL in the web browser.
82 | * @param url String
83 | */
84 | public void openURLinWebBrowser(String url) {
85 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
86 | }
87 |
88 | public void obtainApiKeyByInput(View w) {
89 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
90 | builder.setTitle(getResources().getString(R.string.manual_new_api_key));
91 |
92 | // Set up the input
93 | final EditText input = new EditText(this);
94 |
95 | // Specify the type of input expected
96 | input.setInputType(InputType.TYPE_CLASS_TEXT);
97 | builder.setView(input);
98 |
99 | // Set up the buttons
100 | builder.setPositiveButton(getResources().getString(R.string.dialog_button_ok), new DialogInterface.OnClickListener() {
101 | @Override
102 | public void onClick(DialogInterface dialog, int which) {
103 | String ApiKeyId = input.getText().toString();
104 | // Checking
105 | if (ApiKeyId.equals("")) {
106 | dialog.cancel();
107 | return;
108 | }
109 | // Sending back
110 | Intent intent = new Intent();
111 | intent.putExtra("apikey_id", ApiKeyId);
112 | setResult(RESULT_OK, intent);
113 | finish();
114 | }
115 | });
116 | builder.setNegativeButton(getResources().getString(R.string.dialog_button_cancel), new DialogInterface.OnClickListener() {
117 | @Override
118 | public void onClick(DialogInterface dialog, int which) {
119 | dialog.cancel();
120 | }
121 | });
122 | builder.show();
123 | }
124 |
125 | // Permissions ---------------------------------------------------------------------------------
126 | /**
127 | * Checks if it is allowed to use the camera.
128 | * @return boolean
129 | */
130 | public boolean isCameraPermissionGranted() {
131 | if (Build.VERSION.SDK_INT >= 23) {
132 | if (checkSelfPermission(android.Manifest.permission.CAMERA)
133 | == PackageManager.PERMISSION_GRANTED) {
134 | return true;
135 | } else {
136 | ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, CAMERA_PERMISSION);
137 | return false;
138 | }
139 | } else {
140 | return true;
141 | }
142 | }
143 |
144 | /**
145 | * Continues after the permission is obtained.
146 | * @param requestCode int
147 | * @param permissions @NonNull String[]
148 | * @param grantResults @NonNull int[]
149 | */
150 | @Override
151 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
152 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
153 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
154 | if (requestCode == CAMERA_PERMISSION) {
155 | startQRScanActivity();
156 | }
157 | }
158 | }
159 |
160 | // Menu ----------------------------------------------------------------------------------------
161 |
162 | public boolean onCreateOptionsMenu(Menu menu) {
163 | return true;
164 | }
165 |
166 | public boolean onMenuItemClickShowClientTag(MenuItem v) {
167 | return true;
168 | }
169 |
170 | public boolean onMenuItemClickResetIdentity(MenuItem v) {
171 | return true;
172 | }
173 |
174 | public boolean onMenuItemClickTestArea(MenuItem v) {
175 | return true;
176 | }
177 |
178 | public boolean onMenuItemClickOverrideApiKeyId(MenuItem v) {
179 | return true;
180 | }
181 |
182 | @Override
183 | public boolean onSupportNavigateUp() {
184 | onBackPressed();
185 | return true;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/HelpActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.os.Bundle;
4 | import android.webkit.WebSettings;
5 | import android.webkit.WebView;
6 | import android.webkit.WebViewClient;
7 |
8 | import androidx.appcompat.app.AppCompatActivity;
9 | import androidx.appcompat.widget.Toolbar;
10 |
11 | public class HelpActivity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_help);
17 | Toolbar toolbar = findViewById(R.id.toolbar);
18 | setSupportActionBar(toolbar);
19 |
20 | try {
21 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
22 | getSupportActionBar().setDisplayShowHomeEnabled(true);
23 | } catch (NullPointerException e) {
24 | e.printStackTrace();
25 | }
26 |
27 | // Web view
28 | WebView webView = (WebView)findViewById(R.id.mainWebView);
29 | WebSettings webSettings = webView.getSettings();
30 | webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
31 | // other
32 | webSettings.setJavaScriptEnabled(true);
33 | webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
34 | webSettings.setAllowFileAccessFromFileURLs(true);
35 | webSettings.setAllowUniversalAccessFromFileURLs(true);
36 | // important!
37 | webView.setWebViewClient(new WebViewClient());
38 | webView.loadUrl("file:///android_asset/help/index.html");
39 | }
40 |
41 | @Override
42 | public boolean onSupportNavigateUp() {
43 | onBackPressed();
44 | return true;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/InfoActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.widget.TextView;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 | import androidx.appcompat.widget.Toolbar;
9 |
10 | public class InfoActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_info);
16 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
17 | setSupportActionBar(toolbar);
18 |
19 | try {
20 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
21 | getSupportActionBar().setDisplayShowHomeEnabled(true);
22 | } catch (NullPointerException e) {
23 | e.printStackTrace();
24 | }
25 |
26 | // Getting data from the intent
27 | Intent data = getIntent();
28 | String clientTag = data.getStringExtra("client_tag");
29 |
30 | // Setting the client tag to the text view
31 | TextView clientTagView = (TextView)findViewById(R.id.client_tag_text);
32 | clientTagView.setText(clientTag);
33 | }
34 |
35 | @Override
36 | public boolean onSupportNavigateUp() {
37 | onBackPressed();
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/IntroActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.content.Intent;
4 | import android.content.pm.PackageManager;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.core.app.ActivityCompat;
10 | import androidx.fragment.app.Fragment;
11 | import com.github.paolorotolo.appintro.AppIntro;
12 | import com.github.paolorotolo.appintro.AppIntroFragment;
13 | import com.google.firebase.analytics.FirebaseAnalytics;
14 |
15 | import io.catvision.appl.intro.CustomIntroFragment;
16 |
17 | public class IntroActivity extends AppIntro {
18 | public static int TYPE_NONE = 0;
19 | public static int TYPE_QR = 1;
20 | public static int TYPE_DEEP = 2;
21 |
22 | // Permissions
23 | private static int CAMERA_PERMISSION = 21;
24 | // Requests
25 | private static int QR_CODE_REQUEST = 31;
26 | // Variables
27 | private FirebaseAnalytics mFirebaseAnalytics;
28 | private AppIntroFragment firstFragment;
29 | private AppIntroFragment lastFragment;
30 |
31 | /**
32 | * Does an action depending on the specified type from the fragment.
33 | * @param type int
34 | */
35 | public void click(int type) {
36 | switch (type) {
37 | case 1:
38 | if (isCameraPermissionGranted())
39 | startQRScanActivity();
40 | break;
41 | case 2:
42 | Intent intent = getIntent();
43 | intent.putExtra("type", TYPE_DEEP);
44 | setResult(RESULT_OK, intent);
45 | finish();
46 | break;
47 | default:
48 | break;
49 | }
50 | }
51 |
52 | @Override
53 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
54 | super.onActivityResult(requestCode, resultCode, data);
55 | if (requestCode == QR_CODE_REQUEST) {
56 | if (resultCode == RESULT_OK) {
57 | data.putExtra("type", TYPE_QR); // specify the event
58 | setResult(resultCode, data);
59 | finish();
60 | }
61 | }
62 | }
63 |
64 | @Override
65 | protected void onCreate(@Nullable Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 |
68 | // Note here that we DO NOT use setContentView();
69 | // Add your slide fragments here.
70 | // AppIntro will automatically generate the dots indicator and buttons.
71 | // addSlide(firstFragment);
72 |
73 | // Special custom fragments
74 | // Pairing #1
75 | Fragment customFragment1 = new CustomIntroFragment();
76 | Bundle b1 = new Bundle();
77 | b1.putCharSequence("title", getResources().getString(R.string.intro_third_title));
78 | b1.putCharSequence("description", getResources().getString(R.string.intro_third_text));
79 | b1.putInt("image", R.drawable.cat3);
80 | b1.putInt("color", getResources().getColor(R.color.colorSlide3));
81 | b1.putInt("type", TYPE_QR);
82 | customFragment1.setArguments(b1);
83 |
84 | // Pairing #2
85 | Fragment customFragment2 = new CustomIntroFragment();
86 | Bundle b2 = new Bundle();
87 | b2.putCharSequence("title", getResources().getString(R.string.intro_fourth_title));
88 | b2.putCharSequence("description", getResources().getString(R.string.intro_fourth_text));
89 | b2.putInt("image", R.drawable.cat4);
90 | b2.putInt("color", getResources().getColor(R.color.colorSlide4));
91 | b2.putInt("type", TYPE_DEEP);
92 | customFragment2.setArguments(b2);
93 |
94 | // Instead of fragments, you can also use our default slide
95 | // Just set a title, description, background and image. AppIntro will do the rest.
96 | firstFragment = AppIntroFragment.newInstance(getResources().getString(R.string.intro_first_title), getResources().getString(R.string.intro_first_text), R.drawable.cat1, getResources().getColor(R.color.colorSlide1));
97 | addSlide(firstFragment);
98 | addSlide(AppIntroFragment.newInstance(getResources().getString(R.string.intro_second_title), getResources().getString(R.string.intro_second_text), R.drawable.cat2, getResources().getColor(R.color.colorSlide2)));
99 |
100 | addSlide(customFragment1);
101 | addSlide(customFragment2);
102 |
103 | lastFragment = AppIntroFragment.newInstance(getResources().getString(R.string.intro_fifth_title), getResources().getString(R.string.intro_fifth_text), R.drawable.cat5, getResources().getColor(R.color.colorSlide5));
104 | addSlide(lastFragment);
105 | // setting the title
106 | setTitle(getResources().getString(R.string.intro_title_first));
107 |
108 | // OPTIONAL METHODS
109 | // Override bar/separator color.
110 | setBarColor(getResources().getColor(R.color.colorPrimary));
111 | // setSeparatorColor(Color.parseColor("#2196F3"));
112 |
113 | // Hide Skip/Done button.
114 | showSkipButton(true);
115 | setProgressButtonEnabled(true);
116 |
117 | // Turn vibration on and set intensity.
118 | // NOTE: you will probably need to ask VIBRATE permission in Manifest.
119 | // setVibrate(true);
120 | // setVibrateIntensity(30);
121 |
122 | mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
123 | }
124 |
125 | @Override
126 | public void onRestoreInstanceState(Bundle savedInstanceState) {
127 |
128 | }
129 |
130 | @Override
131 | public void onSaveInstanceState(Bundle outState) {
132 | super.onSaveInstanceState(outState);
133 | }
134 |
135 | @Override
136 | public void onSkipPressed(Fragment currentFragment) {
137 | super.onSkipPressed(currentFragment);
138 | // Do something when users tap on Skip button.
139 | finish();
140 | }
141 |
142 | @Override
143 | public void onDonePressed(Fragment currentFragment) {
144 | super.onDonePressed(currentFragment);
145 | // Do something when users tap on Done button.
146 | finish();
147 | }
148 |
149 | @Override
150 | public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
151 | super.onSlideChanged(oldFragment, newFragment);
152 | // Do something when the slide changes.
153 | try {
154 | if (newFragment != firstFragment && newFragment != lastFragment) {
155 | setTitle(getResources().getString(R.string.intro_title_other));
156 | } else {
157 | setTitle(getResources().getString(R.string.intro_title_first));
158 | }
159 | } catch (NullPointerException e) {
160 | e.printStackTrace();
161 | }
162 | }
163 |
164 | /**
165 | * Starts an activity that retrieves the QR code.
166 | */
167 | public void startQRScanActivity() {
168 | mFirebaseAnalytics.logEvent(getResources().getString(R.string.event_pair_qr), new Bundle());
169 | Intent intent = new Intent(getApplicationContext(), QRCodeScannerActivity.class);
170 | startActivityForResult(intent, QR_CODE_REQUEST);
171 | }
172 |
173 | // Permissions ---------------------------------------------------------------------------------
174 | /**
175 | * Checks if it is allowed to use the camera.
176 | * @return boolean
177 | */
178 | public boolean isCameraPermissionGranted() {
179 | if (Build.VERSION.SDK_INT >= 23) {
180 | if (checkSelfPermission(android.Manifest.permission.CAMERA)
181 | == PackageManager.PERMISSION_GRANTED) {
182 | return true;
183 | } else {
184 | ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, CAMERA_PERMISSION);
185 | return false;
186 | }
187 | } else {
188 | return true;
189 | }
190 | }
191 |
192 | /**
193 | * Continues after the permission is obtained.
194 | * @param requestCode int
195 | * @param permissions @NonNull String[]
196 | * @param grantResults @NonNull int[]
197 | */
198 | @Override
199 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
200 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
201 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
202 | if (requestCode == CAMERA_PERMISSION) {
203 | startQRScanActivity();
204 | }
205 | }
206 | }
207 | }
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/QRCodeScannerActivity.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import com.google.zxing.Result;
8 |
9 | import me.dm7.barcodescanner.zxing.ZXingScannerView;
10 |
11 | // https://github.com/dm77/barcodescanner
12 | public class QRCodeScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
13 | private ZXingScannerView mScannerView;
14 |
15 | @Override
16 | public void onCreate(Bundle state) {
17 | super.onCreate(state);
18 | mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
19 | setContentView(mScannerView); // Set the scanner view as the content view
20 | }
21 |
22 | @Override
23 | public void onResume() {
24 | super.onResume();
25 | mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
26 | mScannerView.startCamera(); // Start camera on resume
27 | }
28 |
29 | @Override
30 | public void onPause() {
31 | super.onPause();
32 | mScannerView.stopCamera(); // Stop camera on pause
33 | }
34 |
35 | @Override
36 | public void handleResult(Result rawResult) {
37 | String format = rawResult.getBarcodeFormat().toString();
38 | if (format.equals("QR_CODE")) {
39 | try {
40 | // Reading the API key ID
41 | Uri uri = Uri.parse(rawResult.getText());
42 | String apikey = uri.getQueryParameter("apikey").replace(" ", "+");
43 | // Returning result
44 | Intent result = new Intent();
45 | result.putExtra("apikey_id", apikey);
46 | result.putExtra("format", format);
47 | setResult(Activity.RESULT_OK, result);
48 | finish();
49 | } catch (Exception e) {
50 | e.printStackTrace();
51 | mScannerView.resumeCameraPreview(this);
52 | }
53 | } else {
54 | // If you would like to resume scanning, call this method below:
55 | mScannerView.resumeCameraPreview(this);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/StartedFragment.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import androidx.fragment.app.Fragment;
11 |
12 |
13 | /**
14 | * A simple {@link Fragment} subclass.
15 | * Activities that contain this fragment must implement the
16 | * {@link StartedFragment.OnFragmentInteractionListener} interface
17 | * to handle interaction events.
18 | * Use the {@link StartedFragment#newInstance} factory method to
19 | * create an instance of this fragment.
20 | */
21 | public class StartedFragment extends Fragment {
22 | // TODO: Rename parameter arguments, choose names that match
23 | // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
24 | private static final String ARG_PARAM1 = "param1";
25 | private static final String ARG_PARAM2 = "param2";
26 |
27 | // TODO: Rename and change types of parameters
28 | private String mParam1;
29 | private String mParam2;
30 |
31 | private OnFragmentInteractionListener mListener;
32 |
33 | public StartedFragment() {
34 | // Required empty public constructor
35 | }
36 |
37 | /**
38 | * Use this factory method to create a new instance of
39 | * this fragment using the provided parameters.
40 | *
41 | * @param param1 Parameter 1.
42 | * @param param2 Parameter 2.
43 | * @return A new instance of fragment StartedFragment.
44 | */
45 | // TODO: Rename and change types and number of parameters
46 | public static StartedFragment newInstance(String param1, String param2) {
47 | StartedFragment fragment = new StartedFragment();
48 | Bundle args = new Bundle();
49 | args.putString(ARG_PARAM1, param1);
50 | args.putString(ARG_PARAM2, param2);
51 | fragment.setArguments(args);
52 | return fragment;
53 | }
54 |
55 | @Override
56 | public void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | if (getArguments() != null) {
59 | mParam1 = getArguments().getString(ARG_PARAM1);
60 | mParam2 = getArguments().getString(ARG_PARAM2);
61 | }
62 | }
63 |
64 | @Override
65 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
66 | Bundle savedInstanceState) {
67 | // Inflate the layout for this fragment
68 | return inflater.inflate(R.layout.fragment_started, container, false);
69 | }
70 |
71 | // TODO: Rename method, update argument and hook method into UI event
72 | public void onButtonPressed(Uri uri) {
73 | if (mListener != null) {
74 | mListener.onFragmentInteractionStopRequest();
75 | }
76 | }
77 |
78 | @Override
79 | public void onAttach(Context context) {
80 | super.onAttach(context);
81 | if (context instanceof OnFragmentInteractionListener) {
82 | mListener = (OnFragmentInteractionListener) context;
83 | } else {
84 | throw new RuntimeException(context.toString()
85 | + " must implement OnFragmentInteractionListener");
86 | }
87 | }
88 |
89 | @Override
90 | public void onDetach() {
91 | super.onDetach();
92 | mListener = null;
93 | }
94 |
95 | /**
96 | * This interface must be implemented by activities that contain this
97 | * fragment to allow an interaction in this fragment to be communicated
98 | * to the activity and potentially other fragments contained in that
99 | * activity.
100 | *
101 | * See the Android Training lesson Communicating with Other Fragments for more information.
104 | */
105 | public interface OnFragmentInteractionListener {
106 | void onFragmentInteractionStopRequest();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/catvision/appl/StoppedFragment.java:
--------------------------------------------------------------------------------
1 | package io.catvision.appl;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 |
10 | import androidx.fragment.app.Fragment;
11 |
12 |
13 | /**
14 | * A simple {@link Fragment} subclass.
15 | * Activities that contain this fragment must implement the
16 | * {@link StoppedFragment.OnFragmentInteractionListener} interface
17 | * to handle interaction events.
18 | * Use the {@link StoppedFragment#newInstance} factory method to
19 | * create an instance of this fragment.
20 | */
21 | public class StoppedFragment extends Fragment {
22 | // TODO: Rename parameter arguments, choose names that match
23 | // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
24 | private static final String ARG_PARAM1 = "param1";
25 | private static final String ARG_PARAM2 = "param2";
26 |
27 | // TODO: Rename and change types of parameters
28 | private String mParam1;
29 | private String mParam2;
30 |
31 | private OnFragmentInteractionListener mListener;
32 |
33 | public StoppedFragment() {
34 | // Required empty public constructor
35 | }
36 |
37 | /**
38 | * Use this factory method to create a new instance of
39 | * this fragment using the provided parameters.
40 | *
41 | * @param param1 Parameter 1.
42 | * @param param2 Parameter 2.
43 | * @return A new instance of fragment StoppedFragment.
44 | */
45 | // TODO: Rename and change types and number of parameters
46 | public static StoppedFragment newInstance(String param1, String param2) {
47 | StoppedFragment fragment = new StoppedFragment();
48 | Bundle args = new Bundle();
49 | args.putString(ARG_PARAM1, param1);
50 | args.putString(ARG_PARAM2, param2);
51 | fragment.setArguments(args);
52 | return fragment;
53 | }
54 |
55 | @Override
56 | public void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | if (getArguments() != null) {
59 | mParam1 = getArguments().getString(ARG_PARAM1);
60 | mParam2 = getArguments().getString(ARG_PARAM2);
61 | }
62 | }
63 |
64 | @Override
65 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
66 | Bundle savedInstanceState) {
67 | // Inflate the layout for this fragment
68 | View view = inflater.inflate(R.layout.fragment_stopped, container, false);
69 | // Checking if the API key is present
70 | refreshApiKeyRelatedView(view);
71 | return view;
72 | }
73 |
74 | public void onClick(View v) {
75 | if (mListener != null) {
76 | mListener.onFragmentInteractionStartRequest();
77 | }
78 | }
79 |
80 | @Override
81 | public void onAttach(Context context) {
82 | super.onAttach(context);
83 | if (context instanceof OnFragmentInteractionListener) {
84 | mListener = (OnFragmentInteractionListener) context;
85 | } else {
86 | throw new RuntimeException(context.toString()
87 | + " must implement OnFragmentInteractionListener");
88 | }
89 | }
90 |
91 | @Override
92 | public void onDetach() {
93 | super.onDetach();
94 | mListener = null;
95 | }
96 |
97 | /**
98 | * This interface must be implemented by activities that contain this
99 | * fragment to allow an interaction in this fragment to be communicated
100 | * to the activity and potentially other fragments contained in that
101 | * activity.
102 | *