├── .gitignore ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── GFKSimpleGame.cpp │ ├── GFKSimpleGame.h │ ├── NearbyConnection.cpp │ ├── NearbyNativeActivity.cpp │ ├── NearbyNativeActivity.h │ ├── NearbyNativeActivity_Engine.cpp │ ├── jui_helper │ │ ├── CMakeLists.txt │ │ ├── JavaUI.cpp │ │ ├── JavaUI.h │ │ ├── JavaUI_Dialog.cpp │ │ ├── JavaUI_View.h │ │ └── JavaUI_Window.cpp │ ├── native-lib.cpp │ └── ndk_helper │ │ ├── CMakeLists.txt │ │ ├── GLContext.cpp │ │ ├── GLContext.h │ │ ├── JNIHelper.cpp │ │ ├── JNIHelper.h │ │ ├── NDKHelper.h │ │ ├── gl3stub.cpp │ │ └── gl3stub.h │ ├── java │ └── com │ │ ├── google │ │ └── example │ │ │ └── games │ │ │ └── nearbyconnections │ │ │ └── NearbyNativeActivity.java │ │ └── sample │ │ └── helper │ │ ├── JUIButton.java │ │ ├── JUIDialog.java │ │ ├── JUIForwardingPopupWindow.java │ │ ├── JUIHelper.java │ │ ├── JUITextView.java │ │ └── NDKHelper.java │ └── res │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gpg-sdk └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | build.xml 4 | gen/ 5 | libs/ 6 | local.properties 7 | obj/ 8 | .settings/ 9 | .DS_Store 10 | 11 | .gradle/ 12 | build/ 13 | .externalNativeBuild/ 14 | 15 | *.iml 16 | 17 | gpg-sdk/gpg-cpp-sdk/ 18 | gpg-sdk/gpg_cpp_sdk.zip 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google Nearby Connections C++ Samples 2 | ===================================== 3 | 4 | This repo has been migrated to [github.com/android/connectivity][1]. Please check that repo for future updates. Thank you! 5 | 6 | [1]: https://github.com/android/connectivity 7 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | ## 15 | 16 | # For more information about using CMake with Android Studio, read the 17 | # documentation: https://d.android.com/studio/projects/add-native-code.html 18 | 19 | # Sets the minimum version of CMake required to build the native library. 20 | 21 | cmake_minimum_required(VERSION 3.4.1) 22 | 23 | #include the native part of JUI Helper 24 | add_subdirectory (src/main/cpp/jui_helper) 25 | 26 | #include the native part of NDKHelper 27 | add_subdirectory (src/main/cpp/ndk_helper) 28 | 29 | #include the GPG C++ SDK 30 | add_library(gpg_sdk STATIC IMPORTED) 31 | set_target_properties(gpg_sdk PROPERTIES IMPORTED_LOCATION 32 | ${GPG_SDK_PATH}/lib/c++/${ANDROID_ABI}/libgpg.a) 33 | 34 | # build native_app_glue as a static lib 35 | add_library(native_app_glue STATIC 36 | ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) 37 | 38 | # build cpufeatures as a static lib 39 | add_library(cpufeatures STATIC 40 | ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c) 41 | 42 | # Export ANativeActivity_onCreate(), 43 | # Refer to: https://github.com/android-ndk/ndk/issues/381. 44 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") 45 | 46 | add_library( 47 | NearbyNativeActivity 48 | SHARED 49 | src/main/cpp/GFKSimpleGame.cpp 50 | src/main/cpp/NearbyConnection.cpp 51 | src/main/cpp/NearbyNativeActivity.cpp 52 | src/main/cpp/NearbyNativeActivity_Engine.cpp 53 | ) 54 | 55 | target_include_directories(NearbyNativeActivity PRIVATE 56 | ${ANDROID_NDK}/sources/android/native_app_glue 57 | ${ANDROID_NDK}/sources/android/cpufeatures 58 | ${GPG_SDK_PATH}/include 59 | src/main/cpp/jui_helper 60 | src/main/cpp/ndk_helper 61 | ) 62 | 63 | target_link_libraries(NearbyNativeActivity 64 | gpg_sdk 65 | native_app_glue 66 | cpufeatures 67 | juihelper 68 | ndkhelper 69 | log 70 | android 71 | EGL 72 | GLESv2 73 | z 74 | ) 75 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 (C) Google LLC 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | apply plugin : 'com.android.application' 17 | 18 | android { 19 | compileSdkVersion 26 20 | defaultConfig { 21 | applicationId "com.google.example.cpp.nearby" 22 | minSdkVersion 14 23 | targetSdkVersion 26 24 | 25 | ndk.abiFilters 'x86', 'armeabi-v7a', 'arm64-v8a' 26 | 27 | externalNativeBuild { 28 | cmake { 29 | cppFlags "-std=c++11 -Wall -frtti -Werror" 30 | arguments "-DANDROID_STL=c++_static", 31 | "-DGPG_SDK_PATH=${project(':gpg-sdk').projectDir}/gpg-cpp-sdk/android", 32 | "-DANDROID_TOOLCHAIN=clang" 33 | } 34 | } 35 | } 36 | externalNativeBuild { 37 | cmake.path "CMakeLists.txt" 38 | } 39 | } 40 | 41 | dependencies { 42 | compile 'com.android.support:appcompat-v7:26.1.0' 43 | compile 'com.google.android.gms:play-services-games:11.8.0' 44 | compile 'com.google.android.gms:play-services-nearby:11.8.0' 45 | } 46 | -------------------------------------------------------------------------------- /app/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/wilkinsonclay/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 37 | 40 | 43 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/cpp/GFKSimpleGame.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* 17 | * GFKSimpleGame.cpp 18 | * A Simple Game to demo Google Nearby Connections Native Interface 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "GFKSimpleGame.h" 28 | #include "JNIHelper.h" 29 | 30 | namespace game_helper { 31 | /* 32 | * supported grade level: currently only supports first grade math level 33 | */ 34 | const int32_t LEVEL_CAP = 2; 35 | const char OPS_CODE[] = {'+', '-', // 1st and 2nd grader 36 | '*', '/', // 3rd and above 37 | '^', 's'}; // 5th 38 | 39 | GFKSimple::GFKSimple() : choice_count_(0), level_(0) { 40 | Init(); 41 | } 42 | 43 | GFKSimple::GFKSimple(int32_t choiceCount, int32_t gradeLevel) 44 | : choice_count_(choiceCount), level_(gradeLevel - 1) { 45 | Init(); 46 | } 47 | 48 | const char *GFKSimple::GetQuestion(void) { 49 | int32_t op1 = GetOperand(); 50 | int32_t op2 = GetOperand(); 51 | char op = GetOperator(); 52 | 53 | snprintf(question_, MAX_QUESTION_LEN, "%d %c %d = ?", op1, op, op2); 54 | int32_t ans; 55 | switch (op) { 56 | case '+': 57 | ans = op1 + op2; 58 | break; 59 | case '-': 60 | ans = op1 - op2; 61 | break; 62 | case '*': 63 | ans = op1 * op2; 64 | break; 65 | case '/': 66 | ans = op1 / op2; 67 | break; 68 | case '^': 69 | op2 %= 4; 70 | snprintf(question_, MAX_QUESTION_LEN, "%d %c %d = ?", op1, op, op2); 71 | ans = pow(op1, op2); 72 | break; 73 | case 's': 74 | ans = sqrt(op1); 75 | snprintf(question_, MAX_QUESTION_LEN, "square_root(%d) = ?", op1); 76 | break; 77 | default: 78 | // Not Recognized, default to '+' 79 | ans = op1 + op2; 80 | LOGE("Wrong operation generated: %c, taking default", op); 81 | break; 82 | } 83 | choices_[choice_count_] = ans; 84 | 85 | int32_t ans_idx = rand() % choice_count_; 86 | choices_[ans_idx] = ans; 87 | for (int32_t i = 0; i < ans_idx; i++) choices_[i] = ans + i - ans_idx; 88 | for (int32_t i = ans_idx + 1; i < choice_count_; i++) 89 | choices_[i] = ans + i - ans_idx; 90 | 91 | return (const char *)&question_[0]; 92 | } 93 | const int32_t *GFKSimple::GetAllChoices(void) { return (const int32_t *)&choices_[0]; } 94 | 95 | void GetSupportedGrade(int32_t *minGrade, int32_t *maxGrade) { 96 | if (!minGrade || !maxGrade) { 97 | LOGE("NULL pointer to GetSupportedGrade()"); 98 | return; 99 | } 100 | if (LEVEL_CAP <= 0) { 101 | *minGrade = *maxGrade = 0; 102 | } 103 | *maxGrade = LEVEL_CAP; 104 | *minGrade = 1; // Grades start with 1 105 | } 106 | 107 | void GFKSimple::Init(void) { 108 | srand((unsigned)time(NULL)); 109 | switch (level_) { 110 | case 0: 111 | default: 112 | max_operand_ = 10; 113 | operation_count_ = 2; 114 | break; 115 | case 1: 116 | max_operand_ = 100; 117 | operation_count_ = 2; 118 | break; 119 | case 3: 120 | max_operand_ = 100; 121 | operation_count_ = 4; 122 | break; 123 | case 4: 124 | case 5: 125 | max_operand_ = 100; 126 | operation_count_ = 6; 127 | break; 128 | } 129 | choices_ = nullptr; 130 | if (choice_count_) { 131 | choices_ = std::unique_ptr(new int32_t[choice_count_ + 1]); 132 | if (choices_) { 133 | memset(choices_.get(), 0, sizeof(int32_t) * (choice_count_ + 1)); 134 | } else { 135 | LOGE("Out of the memory in %s at %d", __FILE__, __LINE__); 136 | } 137 | } 138 | if (operation_count_ > (sizeof(OPS_CODE) / sizeof(OPS_CODE[0]))) { 139 | LOGW("Operation (%d) is bigger than simple game capability(%d)", 140 | operation_count_, static_cast(sizeof(OPS_CODE)/sizeof(OPS_CODE[0]))); 141 | operation_count_ = (sizeof(OPS_CODE) / sizeof(OPS_CODE[0])); 142 | } 143 | } 144 | 145 | int32_t GFKSimple::GetOperand(void) { 146 | return rand() % max_operand_; 147 | } 148 | 149 | char GFKSimple::GetOperator(void) { 150 | return OPS_CODE[rand() % operation_count_]; 151 | } 152 | 153 | bool GFKSimple::SetChoicesPerQuestion(int32_t choiceCount) { 154 | if (choiceCount == choice_count_) { 155 | return true; 156 | } 157 | 158 | std::unique_ptr newChoices = std::unique_ptr(new int32_t[choiceCount + 1]); 159 | if (!newChoices) { 160 | LOGE("Out of memory from GFKSimple::GerOperand"); 161 | return false; 162 | } 163 | choices_ = std::move(newChoices); 164 | choice_count_ = choiceCount; 165 | return true; 166 | } 167 | 168 | int32_t GFKSimple::GetChoicesPerQuestion(void) { 169 | return choice_count_; 170 | } 171 | 172 | /* 173 | * GetCorrectChoice() 174 | * The correct answer index is the extra one returned in the answers -- 175 | * 0 -- choice_count_ - 1 : game choices to be displayed on UI 176 | * choice_count_ : The correct_answer 177 | */ 178 | int32_t GFKSimple::GetCorrectChoice(void) { 179 | if (nullptr != choices_) { 180 | return choice_count_; 181 | } 182 | return 0; 183 | } 184 | 185 | GFKSimple::~GFKSimple() {} 186 | } // namespace game_helper 187 | -------------------------------------------------------------------------------- /app/src/main/cpp/GFKSimpleGame.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* 17 | * GFKSimple.h 18 | * Simple Game Object to show case the nearby connection native interface 19 | */ 20 | #ifndef _USR_LOCAL_GOOGLE_ANDROID_NEARBYCONNECTIONS_JNI_GFKSIMPLEGAME_H_ 21 | #define _USR_LOCAL_GOOGLE_ANDROID_NEARBYCONNECTIONS_JNI_GFKSIMPLEGAME_H_ 22 | 23 | 24 | namespace game_helper { 25 | const int MAX_QUESTION_LEN = 32; 26 | 27 | class GFKSimple { 28 | public: 29 | GFKSimple(); 30 | GFKSimple(int choiceCount, int gradeLevel); 31 | ~GFKSimple(); 32 | 33 | bool SetChoicesPerQuestion(int choiceCount); 34 | int32_t GetChoicesPerQuestion(void); 35 | const char *GetQuestion(void); 36 | const int *GetAllChoices(void); 37 | int32_t GetCorrectChoice(void); 38 | bool GetSupportedGrade(int *minGrade, int *maxGrade); 39 | 40 | private: 41 | std::unique_ptr choices_; 42 | int32_t choice_count_; 43 | char question_[MAX_QUESTION_LEN]; 44 | int32_t level_; 45 | int32_t max_operand_; 46 | int32_t operation_count_; 47 | void Init(void); 48 | int32_t GetOperand(void); 49 | char GetOperator(void); 50 | }; 51 | } // namespace game_helper 52 | #endif // _USR_LOCAL_GOOGLE_ANDROID_NEARBYCONNECTIONS_JNI_GFKSIMPLEGAME_H_ 53 | -------------------------------------------------------------------------------- /app/src/main/cpp/NearbyNativeActivity.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* 17 | * This file demonstrates, 18 | * - How to use Nearby Connection features in C++ code with native client, 19 | * including 20 | * - Create nearby connection interface 21 | * - Set up connections with other devices 22 | * - Handle message sending and receiving 23 | * - Layout simple UI 24 | */ 25 | 26 | /* 27 | * Include files 28 | */ 29 | #include "NearbyNativeActivity.h" 30 | 31 | const int SCORE_STRING_LEN = 32; 32 | 33 | /* 34 | * PlayGame(): Start up game a new game. put it on UI thread since it is UI 35 | * activity 36 | */ 37 | void Engine::PlayGame() { 38 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this]() { 39 | LOGI("Playing match"); 40 | if (dialog_) delete dialog_; 41 | 42 | // Start game 43 | InitializeGame(); 44 | 45 | // 46 | // Using jui_helper, a support library, to create and bind gameplay buttons. 47 | // 48 | dialog_ = new jui_helper::JUIDialog(app_->activity); 49 | 50 | // Setting up labels 51 | time_text_ = new jui_helper::JUITextView("Time Left: 0:00"); 52 | time_text_->AddRule(jui_helper::LAYOUT_PARAMETER_ALIGN_PARENT_TOP, 53 | jui_helper::LAYOUT_PARAMETER_TRUE); 54 | time_text_->AddRule(jui_helper::LAYOUT_PARAMETER_CENTER_IN_PARENT, 55 | jui_helper::LAYOUT_PARAMETER_TRUE); 56 | time_text_->SetAttribute("TextSize", jui_helper::ATTRIBUTE_UNIT_SP, 18.f); 57 | time_text_->SetAttribute("Padding", 10, 10, 10, 10); 58 | 59 | // Adding formula Text 60 | math_formula_ = new jui_helper::JUITextView("10 + 5 = ?"); 61 | math_formula_->AddRule(jui_helper::LAYOUT_PARAMETER_BELOW, time_text_); 62 | math_formula_->AddRule(jui_helper::LAYOUT_PARAMETER_CENTER_IN_PARENT, 63 | jui_helper::LAYOUT_PARAMETER_TRUE); 64 | math_formula_->SetAttribute("TextSize", jui_helper::ATTRIBUTE_UNIT_SP, 65 | 24.f); 66 | math_formula_->SetAttribute("Padding", 10, 10, 10, 10); 67 | 68 | // Adding Multiple Choice Buttons 69 | jui_helper::JUIButton *button = CreateChoiceButton("A", NULL); 70 | if (button) { 71 | button->AddRule(jui_helper::LAYOUT_PARAMETER_ALIGN_PARENT_LEFT, 72 | jui_helper::LAYOUT_PARAMETER_TRUE); 73 | button->SetMargins(40, 0, 0, 0); // todo: make it adaptive 74 | } 75 | game_buttons_[0] = button; 76 | for (int i = 1; i < CHOICES_PER_QUESTION; i++) { 77 | std::string cap(1, 'A' + i); 78 | button = CreateChoiceButton(cap.c_str(), game_buttons_[i - 1]); 79 | game_buttons_[i] = button; 80 | } 81 | 82 | const int32_t labelWidth = 600; 83 | const int32_t labelHeight = 300; 84 | scores_text_ = new jui_helper::JUITextView("0:00"); 85 | scores_text_->AddRule(jui_helper::LAYOUT_PARAMETER_BELOW, 86 | game_buttons_[CHOICES_PER_QUESTION - 1]); 87 | scores_text_->AddRule(jui_helper::LAYOUT_PARAMETER_CENTER_IN_PARENT, 88 | jui_helper::LAYOUT_PARAMETER_TRUE); 89 | scores_text_->SetAttribute("TextSize", jui_helper::ATTRIBUTE_UNIT_SP, 18.f); 90 | scores_text_->SetAttribute("MinimumWidth", labelWidth); 91 | scores_text_->SetAttribute("MinimumHeight", labelHeight); 92 | scores_text_->SetAttribute("Padding", 10, 10, 10, 10); 93 | 94 | UpdateScoreBoardUI(false); 95 | 96 | dialog_->AddView(math_formula_); 97 | 98 | std::for_each( 99 | game_buttons_, game_buttons_ + CHOICES_PER_QUESTION, 100 | [this](jui_helper::JUIButton *button) { dialog_->AddView(button); }); 101 | PlayOneRound(); 102 | 103 | dialog_->AddView(time_text_); 104 | dialog_->AddView(scores_text_); 105 | 106 | dialog_->SetAttribute("Title", "Select an answer"); 107 | dialog_->SetCallback( 108 | jui_helper::JUICALLBACK_DIALOG_DISMISSED, 109 | [this](jui_helper::JUIDialog *dialog, const int32_t message) { 110 | LOGI("Dialog dismissed"); 111 | LeaveGame(); 112 | dialog_ = nullptr; 113 | }); 114 | 115 | dialog_->Show(); 116 | 117 | // 118 | // Invoke time counter periodically 119 | // 120 | std::thread([this]() { 121 | ndk_helper::JNIHelper &helper = *ndk_helper::JNIHelper::GetInstance(); 122 | helper.AttachCurrentThread(); 123 | while (playing_ && game_time_ <= GAME_DURATION) { 124 | // Update game UI, UI update needs to be performed in UI thread 125 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this]() { 126 | char str[SCORE_STRING_LEN]; 127 | std::lock_guard lock(mutex_); 128 | snprintf(str, SCORE_STRING_LEN, "Time Left: %d", 129 | GAME_DURATION - this->game_time_); 130 | time_text_->SetAttribute("Text", const_cast(str)); 131 | }); 132 | 133 | // maintain our private clock 134 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 135 | game_time_ += 1; 136 | } 137 | 138 | // finish game 139 | playing_ = false; 140 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this]() { 141 | std::lock_guard lock(mutex_); 142 | for (int i = 0; i < CHOICES_PER_QUESTION; i++) { 143 | game_buttons_[i]->SetAttribute("Enabled", false); 144 | } 145 | math_formula_->SetAttribute("Enabled", false); 146 | }); 147 | 148 | BroadcastScore(score_counter_, true); 149 | 150 | UpdateScoreBoardUI(true); 151 | helper.DetachCurrentThread(); 152 | }).detach(); 153 | }); 154 | } 155 | 156 | /* 157 | * UpdateScoreBoardUI() 158 | * Update game score UI when some player's score changed [ mine or other 159 | * players 160 | * from remote ends]. Scores are already saved in our own cache, just pull out 161 | * to display 162 | */ 163 | void Engine::UpdateScoreBoardUI(bool UIThreadRequired) { 164 | // Lock mutex since this one can be called from multiple thread, 165 | // gpg callback tread and UI callback thread 166 | std::lock_guard lock(mutex_); 167 | if (dialog_ == nullptr) return; 168 | 169 | const int32_t SCORE_SIZE = 64; 170 | char str[SCORE_SIZE]; 171 | snprintf(str, SCORE_SIZE, "%03d", score_counter_); 172 | std::string str_myscore(str); 173 | 174 | snprintf(str, SCORE_SIZE, "My score: %03d %s\n", score_counter_, 175 | playing_ ? "" : "*"); 176 | std::string allstr(str); 177 | 178 | RetrieveScores(allstr); 179 | if (!UIThreadRequired) { 180 | scores_text_->SetAttribute("Text", 181 | const_cast(allstr.c_str())); 182 | return; 183 | } 184 | 185 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread( 186 | [this, str_myscore, allstr]() { 187 | scores_text_->SetAttribute("Text", 188 | const_cast(allstr.c_str())); 189 | }); 190 | } 191 | 192 | /* 193 | * Initialize game state 194 | */ 195 | void Engine::InitializeGame() { 196 | std::lock_guard lock(mutex_); 197 | playing_ = true; 198 | game_time_ = 0; 199 | score_counter_ = 0; 200 | } 201 | 202 | /* 203 | * Leave game 204 | */ 205 | void Engine::LeaveGame() { 206 | std::lock_guard lock(mutex_); 207 | LOGI("Game is over"); 208 | playing_ = false; 209 | } 210 | 211 | /* 212 | * Initialize game management UI, 213 | * invoking jui_helper functions to create java UIs 214 | */ 215 | void Engine::InitUI() { 216 | 217 | const int32_t LEFT_MARGIN = 20; 218 | 219 | // The window initialization 220 | jui_helper::JUIWindow::Init(app_->activity, JUIHELPER_CLASS_NAME); 221 | 222 | // Using jui_helper, a support library, to create and bind game management UIs 223 | int32_t win_width = ANativeWindow_getWidth(app_->window); 224 | int32_t win_height = ANativeWindow_getHeight(app_->window); 225 | 226 | if (win_height <= 0 || win_width <= 0) { 227 | LOGE("Failed to get native window size"); 228 | return; 229 | } 230 | if (win_height > win_width) { 231 | int32_t tmp = win_width; 232 | win_width = win_height; 233 | win_height = tmp; 234 | } 235 | 236 | int32_t button_raw_width = win_width / 4; // we have 4 buttons 237 | int32_t button_height = win_height / 4; 238 | int cur_idx = 0; 239 | 240 | // Create 4 buttons to control nearby sign-in 241 | // The sequence is dictated by enum BUTTON_INDEX, 242 | // it MUST match the button titles array defined here 243 | const char *titles[UI_BUTTON_COUNT] = {"Advertise", "Discover", "Play Game", 244 | "Stop"}; 245 | std::function button_handlers[] = { 246 | [this](jui_helper::JUIView *button, const int32_t msg) { 247 | if (msg == jui_helper::JUICALLBACK_BUTTON_UP) { 248 | OnAdvertiseButtonClick(); 249 | } 250 | }, 251 | [this](jui_helper::JUIView *button, const int32_t msg) { 252 | if (msg == jui_helper::JUICALLBACK_BUTTON_UP) { 253 | OnDiscoverButtonClick(); 254 | } 255 | }, 256 | [this](jui_helper::JUIView *button, const int32_t msg) { 257 | if (msg == jui_helper::JUICALLBACK_BUTTON_UP) { 258 | OnPlayButtonClick(); 259 | } 260 | }, 261 | [this](jui_helper::JUIView *button, const int32_t msg) { 262 | if (msg == jui_helper::JUICALLBACK_BUTTON_UP) { 263 | OnStopButtonClick(); 264 | } 265 | }, 266 | }; 267 | 268 | for (cur_idx = 0; cur_idx < UI_BUTTON_COUNT; cur_idx++) { 269 | jui_helper::JUIButton *button = new jui_helper::JUIButton(titles[cur_idx]); 270 | button->AddRule(jui_helper::LAYOUT_PARAMETER_CENTER_VERTICAL, 271 | jui_helper::LAYOUT_PARAMETER_TRUE); 272 | button->AddRule(jui_helper::LAYOUT_PARAMETER_ALIGN_PARENT_LEFT, 273 | jui_helper::LAYOUT_PARAMETER_TRUE); 274 | button->SetAttribute("MinimumWidth", button_raw_width - LEFT_MARGIN); 275 | button->SetAttribute("MinimumHeight", button_height); 276 | button->SetMargins(LEFT_MARGIN + cur_idx * button_raw_width, 0, 0, 0); 277 | button->SetCallback(button_handlers[cur_idx]); 278 | jui_helper::JUIWindow::GetInstance()->AddView(button); 279 | ui_buttons_[cur_idx] = button; 280 | } 281 | 282 | status_text_ = new jui_helper::JUITextView("Nearby Connection is Idle"); 283 | status_text_->AddRule(jui_helper::LAYOUT_PARAMETER_ALIGN_PARENT_BOTTOM, 284 | jui_helper::LAYOUT_PARAMETER_TRUE); 285 | status_text_->AddRule(jui_helper::LAYOUT_PARAMETER_CENTER_IN_PARENT, 286 | jui_helper::LAYOUT_PARAMETER_TRUE); 287 | status_text_->SetAttribute("TextSize", jui_helper::ATTRIBUTE_UNIT_SP, 17.f); 288 | jui_helper::JUIWindow::GetInstance()->AddView(status_text_); 289 | 290 | // Init nearby connections... 291 | std::thread([this]() { 292 | ndk_helper::JNIHelper &helper = *ndk_helper::JNIHelper::GetInstance(); 293 | helper.AttachCurrentThread(); 294 | 295 | InitGoogleNearbyConnection(); 296 | 297 | helper.DetachCurrentThread(); 298 | }).detach(); 299 | 300 | EnableUI(true); 301 | return; 302 | } 303 | 304 | /* 305 | * Enable/Disable management UI 306 | */ 307 | void Engine::EnableUI(bool enable) { 308 | LOGI("Updating UI:%d", enable); 309 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this, enable]() { 310 | ui_buttons_[BUTTON_ADVERTISE]->SetAttribute( 311 | "Enabled", 312 | enable && !(nbc_state_ & nearby_connection_state::ADVERTISING)); 313 | ui_buttons_[BUTTON_DISCOVER]->SetAttribute( 314 | "Enabled", 315 | enable && !(nbc_state_ & nearby_connection_state::DISCOVERING)); 316 | ui_buttons_[BUTTON_PLAY_GAME]->SetAttribute( 317 | "Enabled", enable && (nbc_state_ & nearby_connection_state::CONNECTED)); 318 | /* 319 | * For experimental purpose, Stop button is always enabled 320 | */ 321 | ui_buttons_[BUTTON_STOP]->SetAttribute("Enabled", true); 322 | 323 | std::string str; 324 | str += "Nearby Connection Status: Connected Clients = "; 325 | str += std::to_string(CountConnections()); 326 | str += "; "; 327 | if (nbc_state_ & nearby_connection_state::IDLE) { 328 | str += "Currently Idle; "; 329 | } 330 | if (nbc_state_ & nearby_connection_state::ADVERTISING) { 331 | str += "In Advertising; "; 332 | } 333 | if (nbc_state_ & nearby_connection_state::DISCOVERING) { 334 | str += "In Discovering; "; 335 | } 336 | if (nbc_state_ & nearby_connection_state::FAILED) { 337 | str += "FAILED; "; 338 | } 339 | str = str.substr(0, str.size() - 2); 340 | status_text_->SetAttribute("Text", const_cast(str.c_str())); 341 | }); 342 | } 343 | 344 | /* 345 | * Help function to create(multiple choice buttons) 346 | */ 347 | jui_helper::JUIButton *Engine::CreateChoiceButton(const char *cap, 348 | jui_helper::JUIButton *left, 349 | float fontSize) { 350 | jui_helper::JUIButton *button = new jui_helper::JUIButton(cap); 351 | if (!button) { 352 | LOGE("Out of Memory in %s @ line %d", __FILE__, __LINE__); 353 | return NULL; 354 | } 355 | button->SetCallback([this](jui_helper::JUIView *view, const int32_t msg) { 356 | switch (msg) { 357 | case jui_helper::JUICALLBACK_BUTTON_UP: 358 | if (!playing_) return; 359 | CheckChoice(static_cast(view)); 360 | PlayOneRound(); 361 | } 362 | }); 363 | if (left) button->AddRule(jui_helper::LAYOUT_PARAMETER_RIGHT_OF, left); 364 | button->AddRule(jui_helper::LAYOUT_PARAMETER_BELOW, math_formula_); 365 | button->SetAttribute("TextSize", jui_helper::ATTRIBUTE_UNIT_SP, fontSize); 366 | button->SetAttribute("Padding", 2, 5, 2, 5); 367 | button->SetMargins(0, 0, 0, 0); 368 | return button; 369 | } 370 | 371 | /* 372 | * Play one round of game: generate a questions and config them to UI 373 | */ 374 | bool Engine::PlayOneRound(void) { 375 | const int32_t CHOICE_LEN = 16; 376 | math_formula_->SetAttribute("Text", game_->GetQuestion()); 377 | const int *allChoices = game_->GetAllChoices(); 378 | for (int i = 0; i < game_->GetChoicesPerQuestion(); i++) { 379 | char choice[CHOICE_LEN]; 380 | snprintf(choice, CHOICE_LEN, "%d", allChoices[i]); 381 | game_buttons_[i]->SetAttribute("Text", (const char*)choice); 382 | game_buttons_[i]->SetAttribute("Enabled", true); 383 | } 384 | return true; 385 | } 386 | /* 387 | * Check the selection is the correct answer and update our local score 388 | */ 389 | void Engine::CheckChoice(jui_helper::JUIButton *selection) { 390 | int idx = 0; 391 | while (idx < CHOICES_PER_QUESTION && game_buttons_[idx] != selection) { 392 | ++idx; 393 | } 394 | const int *allChoices = game_->GetAllChoices(); 395 | if (allChoices[idx] == allChoices[game_->GetCorrectChoice()]) { 396 | // Update my own UI score 397 | ++score_counter_; 398 | 399 | // Broadcast this new score to other players 400 | BroadcastScore(score_counter_, false); 401 | UpdateScoreBoardUI(true); 402 | } 403 | } 404 | 405 | /* 406 | * JNI functions those manage activity lifecycle 407 | */ 408 | extern "C" { 409 | /* 410 | * These callbacks are necessary to work Google Play Game Services UIs properly 411 | * 412 | * For apps which target Android 2.3 or 3.x devices (API Version prior to 14), 413 | * Play Game Services has no way to automatically receive Activity lifecycle 414 | * callbacks. In these cases, Play Game Services relies on the owning Activity 415 | * to notify it of lifecycle events. Any Activity which owns a GameServices 416 | * object should call the AndroidSupport::* functions from within their own 417 | * lifecycle callback functions. The arguments in these functions match those 418 | * provided by Android, so no additional processing is necessary. 419 | */ 420 | JNIEXPORT void 421 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityResult( 422 | JNIEnv *env, jobject thiz, jobject activity, jint requestCode, 423 | jint resultCode, jobject data) { 424 | gpg::AndroidSupport::OnActivityResult(env, activity, requestCode, resultCode, 425 | data); 426 | } 427 | 428 | JNIEXPORT void 429 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityCreated( 430 | JNIEnv *env, jobject thiz, jobject activity, jobject saved_instance_state) { 431 | gpg::AndroidSupport::OnActivityCreated(env, activity, saved_instance_state); 432 | } 433 | 434 | JNIEXPORT void 435 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityDestroyed( 436 | JNIEnv *env, jobject thiz, jobject activity) { 437 | gpg::AndroidSupport::OnActivityDestroyed(env, activity); 438 | } 439 | 440 | JNIEXPORT void 441 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityPaused( 442 | JNIEnv *env, jobject thiz, jobject activity) { 443 | gpg::AndroidSupport::OnActivityPaused(env, activity); 444 | } 445 | 446 | JNIEXPORT void 447 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityResumed( 448 | JNIEnv *env, jobject thiz, jobject activity) { 449 | gpg::AndroidSupport::OnActivityResumed(env, activity); 450 | } 451 | 452 | JNIEXPORT void 453 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivitySaveInstanceState( 454 | JNIEnv *env, jobject thiz, jobject activity, jobject out_state) { 455 | gpg::AndroidSupport::OnActivitySaveInstanceState(env, activity, out_state); 456 | } 457 | 458 | JNIEXPORT void 459 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityStarted( 460 | JNIEnv *env, jobject thiz, jobject activity) { 461 | gpg::AndroidSupport::OnActivityStarted(env, activity); 462 | } 463 | 464 | JNIEXPORT void 465 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_nativeOnActivityStopped( 466 | JNIEnv *env, jobject thiz, jobject activity) { 467 | gpg::AndroidSupport::OnActivityStopped(env, activity); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /app/src/main/cpp/NearbyNativeActivity.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | /* 19 | * Include files 20 | */ 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // For GPGS 31 | #include "gpg/gpg.h" 32 | #include "GFKSimpleGame.h" 33 | #include "NDKHelper.h" 34 | #include "JavaUI.h" 35 | 36 | /* 37 | * Preprocessors 38 | */ 39 | 40 | // Class name of helper function 41 | #define HELPER_CLASS_NAME "com.sample.helper.NDKHelper" 42 | // Class name of JUIhelper function 43 | #define JUIHELPER_CLASS_NAME "com.sample.helper.JUIHelper" 44 | // Share object name of helper function library 45 | #define HELPER_CLASS_SONAME "NearbyNativeActivity" 46 | 47 | #define PAYLOAD_HEADER_LENGTH 1 48 | #define PAYLOAD_HEADER_NEW_CONNECTION 'c' 49 | #define PAYLOAD_HEADER_DISCONNECTED 'd' 50 | #define PAYLOAD_HEADER_RENEW_CONNECTION 'r' 51 | #define PAYLOAD_HEADER_GUEST_READY 'n' 52 | #define PAYLOAD_HEADER_INTERMEDIATE_SCORE 'i' 53 | #define PAYLOAD_HEADER_FINAL_SCORE 'f' 54 | 55 | const int32_t GAME_DURATION = 30; 56 | const int32_t CHOICES_PER_QUESTION = 4; 57 | 58 | struct PLAYER_STATUS { 59 | std::string device_id_; 60 | std::string endpoint_id_; 61 | int32_t score_; 62 | bool connected_; // this node [endpoint_id_] is still online 63 | bool finished_; // this node finished the game 64 | bool is_host_; // I am hosting this link[I was advertising and this node was 65 | // discovering 66 | bool is_direct_connection_; // this node directly connects to me, 67 | // not relayed to me by another device 68 | }; 69 | 70 | class LogFunc { 71 | public: 72 | explicit LogFunc(const char *func_name) { 73 | func_name_ = std::string(func_name); 74 | LOGI("===>%s", func_name_.c_str()); 75 | } 76 | ~LogFunc() { LOGI("<==%s", func_name_.c_str()); } 77 | 78 | private: 79 | std::string func_name_; 80 | }; 81 | 82 | /* 83 | * Engine class of the sample: my class should be IEndpointDiscoverListener() 84 | */ 85 | struct android_app; 86 | class Engine : public gpg::IEndpointDiscoveryListener { 87 | public: 88 | /* 89 | * nearby_connection_state are bit flags, they could co-exist. 90 | */ 91 | enum nearby_connection_state { 92 | IDLE = 1, 93 | ADVERTISING = 2, 94 | DISCOVERING = 4, 95 | CONNECTED = 8, 96 | PLAYING = 16, 97 | FAILED = 32 98 | }; 99 | 100 | // GPG-related methods 101 | void InitGoogleNearbyConnection(); 102 | 103 | void InitializeGame(); 104 | void PlayGame(); 105 | void LeaveGame(); 106 | 107 | // Event handling 108 | static void HandleCmd(struct android_app *app, int32_t cmd); 109 | static int32_t HandleInput(android_app *app, AInputEvent *event); 110 | 111 | // Engine life cycles 112 | Engine(); 113 | ~Engine(); 114 | void SetState(android_app *state); 115 | void InitDisplay(const int32_t cmd); 116 | void DrawFrame(); 117 | void TermDisplay(const int32_t cmd); 118 | bool IsReady(); 119 | 120 | // IEndpointDiscoverListener members 121 | virtual void OnEndpointFound(int64_t client_id, 122 | gpg::EndpointDetails const &endpoint_details); 123 | virtual void OnEndpointLost(int64_t client_id, 124 | std::string const &remote_endpoint_id); 125 | 126 | // MessageListnerHelper to handle the messages 127 | void OnMessageDisconnectedCallback(int64_t receiver_id, 128 | std::string const &remote_endpoint); 129 | void OnMessageReceivedCallback(int64_t receiver_id, 130 | std::string const &remote_endpoint, 131 | std::vector const &payload, 132 | bool is_reliable); 133 | void OnStopButtonClick(void); 134 | 135 | private: 136 | void OnAdvertiseButtonClick(void); 137 | void OnDiscoverButtonClick(void); 138 | void OnPlayButtonClick(void); 139 | 140 | void BroadcastNewConnection(std::string const &endpoint); 141 | void SendAllConnections(const std::string& accepting_endpoint_id); 142 | void OnConnectionResponse(gpg::ConnectionResponse const &response); 143 | void ProcessEndPointNotconnected(std::string const &remote_endpoint_id); 144 | void AddConnectionEndpoint(std::string const &remote_endpoint_id, 145 | bool is_native, bool is_host); 146 | void RemoveConnectionEndpoint(std::string const &remote_endpoint_id, 147 | bool need_broadcast = false); 148 | int32_t CountConnections(void); 149 | void UpdatePlayerScore(std::string const & endpoint_id, 150 | int score, bool final); 151 | void BroadcastScore(int32_t score, bool final); 152 | void RetrieveScores(std::string &score_str); 153 | bool BuildScorePayload(std::vector &payload, int score, 154 | std::string const & endpoint, bool final); 155 | bool DecodeScorePayload(std::vector const &payload, int *p_score, 156 | const std::string &endpoint); 157 | 158 | void DebugDumpConnections(void); 159 | 160 | void InitUI(); 161 | void EnableUI(bool enable); 162 | void UpdateScoreBoardUI(bool UIThreadRequired); 163 | 164 | jui_helper::JUIButton *CreateChoiceButton(const char *cap, 165 | jui_helper::JUIButton *left, 166 | float fontSize = 17.f); 167 | void CheckChoice(jui_helper::JUIButton *selection); 168 | bool PlayOneRound(void); 169 | 170 | std::unique_ptr nearby_connection_; 171 | 172 | // hashmap to keep tracking of player scores 173 | std::unordered_map players_score_; 174 | int32_t score_counter_; // Score counter of local player 175 | bool playing_; // Am I playing a game? 176 | int game_time_; // Game start time 177 | game_helper::GFKSimple *game_; 178 | 179 | mutable std::mutex mutex_; 180 | 181 | // GLContext instance 182 | ndk_helper::GLContext *gl_context_; 183 | 184 | bool initialized_resources_; 185 | bool has_focus_; 186 | 187 | // Native activity app instance 188 | android_app *app_; 189 | 190 | // JUI dialog-related UI stuff here 191 | jui_helper::JUIDialog *dialog_; 192 | 193 | enum BUTTON_INDEX { 194 | BUTTON_ADVERTISE = 0, 195 | BUTTON_DISCOVER, 196 | BUTTON_PLAY_GAME, 197 | BUTTON_STOP, 198 | UI_BUTTON_COUNT 199 | }; 200 | jui_helper::JUIButton *ui_buttons_[UI_BUTTON_COUNT]; 201 | jui_helper::JUITextView *status_text_; 202 | 203 | jui_helper::JUITextView *time_text_; 204 | jui_helper::JUITextView *math_formula_; 205 | jui_helper::JUITextView *scores_text_; 206 | jui_helper::JUIButton *game_buttons_[CHOICES_PER_QUESTION]; 207 | 208 | std::string service_id_; 209 | uint32_t nbc_state_; 210 | gpg::MessageListenerHelper msg_listener_; 211 | gpg::EndpointDiscoveryListenerHelper *discovery_helper_; 212 | }; 213 | -------------------------------------------------------------------------------- /app/src/main/cpp/NearbyNativeActivity_Engine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* 17 | * Include files 18 | */ 19 | #include "NearbyNativeActivity.h" 20 | 21 | const int SELECTION_PER_QUESTION = 4; 22 | const int MATH_GRADE_LEVEL = 2; 23 | /* 24 | * Ctor 25 | */ 26 | Engine::Engine() 27 | : initialized_resources_(false), 28 | has_focus_(false), 29 | app_(nullptr), 30 | dialog_(nullptr), 31 | status_text_(nullptr) { 32 | gl_context_ = ndk_helper::GLContext::GetInstance(); 33 | game_ = new game_helper::GFKSimple(SELECTION_PER_QUESTION, MATH_GRADE_LEVEL); 34 | if (game_ == nullptr) { 35 | LOGE("Out of Memory in %s @line %d", __FILE__, __LINE__); 36 | return; 37 | } 38 | memset(ui_buttons_, 0, sizeof(ui_buttons_)); 39 | memset(game_buttons_, 0, sizeof(game_buttons_)); 40 | nbc_state_ = nearby_connection_state::IDLE; 41 | } 42 | 43 | /* 44 | * Dtor 45 | */ 46 | Engine::~Engine() { 47 | // Clean up nearby connection ( PIGGYBACK on button stop action ) 48 | OnStopButtonClick(); 49 | delete game_; 50 | jui_helper::JUIWindow::GetInstance()->Close(); 51 | delete dialog_; 52 | } 53 | 54 | /** 55 | * Initialize an EGL context for the current display. 56 | */ 57 | void Engine::InitDisplay(const int32_t cmd) { 58 | if (!initialized_resources_) { 59 | gl_context_->Init(app_->window); 60 | InitUI(); 61 | initialized_resources_ = true; 62 | } else { 63 | if (EGL_SUCCESS != gl_context_->Resume(app_->window)) { 64 | LOGE("Failed To Re-Initialize OpenGL functions"); 65 | } 66 | 67 | jui_helper::JUIWindow::GetInstance()->Resume(app_->activity, cmd); 68 | } 69 | 70 | // Enable culling OpenGL state 71 | glEnable(GL_CULL_FACE); 72 | 73 | // Enabled depth test OpenGL state 74 | glEnable(GL_DEPTH_TEST); 75 | glDepthFunc(GL_LEQUAL); 76 | 77 | // Note that screen size might have been changed 78 | glViewport(0, 0, gl_context_->GetScreenWidth(), 79 | gl_context_->GetScreenHeight()); 80 | } 81 | 82 | /** 83 | * Just the current frame in the display. 84 | */ 85 | void Engine::DrawFrame() { 86 | float bkColor = .5f; 87 | glClearColor(bkColor, bkColor, bkColor, 1.f); 88 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 89 | gl_context_->Swap(); 90 | } 91 | 92 | /** 93 | * Tear down the EGL context currently associated with the display. 94 | */ 95 | void Engine::TermDisplay(const int32_t cmd) { 96 | gl_context_->Suspend(); 97 | jui_helper::JUIWindow::GetInstance()->Suspend(cmd); 98 | } 99 | 100 | /** 101 | * HandleInput(): processing any gesture event we are interested in. For this 102 | * App, all event are handled on Java side 103 | */ 104 | int32_t Engine::HandleInput(android_app *app, AInputEvent *event) { 105 | return 0; 106 | } 107 | 108 | /** 109 | * Process the next main command. 110 | */ 111 | void Engine::HandleCmd(struct android_app *app, int32_t cmd) { 112 | Engine *eng = reinterpret_cast (app->userData); 113 | LOGI("message %d", cmd); 114 | switch (cmd) { 115 | case APP_CMD_INIT_WINDOW: 116 | if (app->window != NULL) { 117 | eng->InitDisplay(APP_CMD_INIT_WINDOW); 118 | eng->DrawFrame(); 119 | } 120 | break; 121 | case APP_CMD_TERM_WINDOW: 122 | // Disconnect all connections before going down 123 | eng->OnStopButtonClick(); 124 | // Note that JUI helper needs to know if a window has been terminated 125 | eng->TermDisplay(APP_CMD_TERM_WINDOW); 126 | eng->has_focus_ = false; 127 | break; 128 | case APP_CMD_RESUME: 129 | jui_helper::JUIWindow::GetInstance()->Resume(app->activity, 130 | APP_CMD_RESUME); 131 | // Players need re-connect since we teared down the connection when we 132 | // went suspension 133 | eng->EnableUI(true); 134 | break; 135 | case APP_CMD_GAINED_FOCUS: 136 | // Start animation 137 | eng->has_focus_ = true; 138 | jui_helper::JUIWindow::GetInstance()->Resume(app->activity, 139 | APP_CMD_GAINED_FOCUS); 140 | break; 141 | case APP_CMD_LOST_FOCUS: 142 | // Also stop animating. 143 | eng->has_focus_ = false; 144 | eng->DrawFrame(); 145 | break; 146 | case APP_CMD_CONFIG_CHANGED: 147 | // Configuration changes 148 | eng->TermDisplay(APP_CMD_CONFIG_CHANGED); 149 | eng->InitDisplay(APP_CMD_CONFIG_CHANGED); 150 | break; 151 | case APP_CMD_DESTROY: 152 | ndk_helper::JNIHelper::GetInstance()->DetachCurrentThread(); 153 | break; 154 | } 155 | } 156 | 157 | /* 158 | * Misc 159 | */ 160 | void Engine::SetState(android_app *state) { app_ = state; } 161 | 162 | /* 163 | * disable 3D rendering for now 164 | */ 165 | bool Engine::IsReady() { 166 | if (has_focus_) return true; 167 | 168 | return false; 169 | } 170 | 171 | /* 172 | * Our global instance for Game engine 173 | */ 174 | Engine g_engine; 175 | 176 | /** 177 | * This is the main entry point of a native application that is using 178 | * android_native_app_glue. It runs in its own thread, with its own 179 | * event loop for receiving input events and doing other things. 180 | */ 181 | void android_main(android_app *state) { 182 | 183 | g_engine.SetState(state); 184 | 185 | // Init helper functions 186 | ndk_helper::JNIHelper::Init(state->activity, HELPER_CLASS_NAME, 187 | HELPER_CLASS_SONAME); 188 | 189 | // Init play game services 190 | g_engine.InitGoogleNearbyConnection(); 191 | 192 | state->userData = &g_engine; 193 | state->onAppCmd = Engine::HandleCmd; 194 | state->onInputEvent = Engine::HandleInput; 195 | 196 | // loop waiting for stuff to do. 197 | while (1) { 198 | // Read all pending events. 199 | int id; 200 | int events; 201 | android_poll_source *source; 202 | 203 | // If not animating, we will block forever waiting for events. 204 | // If animating, we loop until all events are read, then continue 205 | // to draw the next frame of animation. 206 | while ((id = ALooper_pollAll(g_engine.IsReady() ? 0 : -1, NULL, &events, 207 | reinterpret_cast(&source))) >= 0) { 208 | // Process this event. 209 | if (source != NULL) source->process(state, source); 210 | 211 | // Check if we are exiting. 212 | if (state->destroyRequested != 0) { 213 | g_engine.TermDisplay(APP_CMD_TERM_WINDOW); 214 | return; 215 | } 216 | } 217 | 218 | if (g_engine.IsReady()) { 219 | // Drawing is throttled to the screen update rate, so there 220 | // is no need to do timing here. 221 | g_engine.DrawFrame(); 222 | } 223 | } 224 | } 225 | 226 | extern "C" { 227 | JNIEXPORT void 228 | Java_com_google_example_games_nearbyconnections_NearbyNativeActivity_OnPauseHandler( 229 | JNIEnv *env) { 230 | // This call is to suppress 'E/WindowManager(): android.view.WindowLeaked...' 231 | // errors. 232 | // Since orientation change events in NativeActivity comes later than 233 | // expected, we can not dismiss 234 | // popupWindow gracefully from NativeActivity. 235 | // So we are releasing popupWindows explicitly triggered from Java callback 236 | // through JNI call. 237 | jui_helper::JUIWindow::GetInstance()->Suspend(APP_CMD_PAUSE); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | ## 15 | 16 | # For more information about using CMake with Android Studio, read the 17 | # documentation: https://d.android.com/studio/projects/add-native-code.html 18 | 19 | # Sets the minimum version of CMake required to build the native library. 20 | 21 | cmake_minimum_required(VERSION 3.4.1) 22 | set(CMAKE_VERBOSE_MAKEFILE on) 23 | 24 | project(juihelper) 25 | 26 | ##include the native part of NDKHelper 27 | #add_subdirectory (${NDK_HELPER_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}/libndkhelper") 28 | 29 | add_library(juihelper STATIC 30 | JavaUI.cpp 31 | JavaUI_Dialog.cpp 32 | JavaUI_Window.cpp 33 | ) 34 | 35 | target_include_directories(juihelper PRIVATE 36 | ${ANDROID_NDK}/sources/android/native_app_glue 37 | ${ndkhelper_SOURCE_DIR}/src/main/cpp 38 | ../ndk_helper 39 | ) 40 | set_target_properties(juihelper 41 | PROPERTIES 42 | ARCHIVE_OUTPUT_DIRECTORY 43 | "${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}") 44 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/JavaUI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "JavaUI.h" 18 | 19 | namespace jui_helper { 20 | 21 | /* 22 | * Callback Handler for Java events 23 | */ 24 | extern "C" { 25 | JNIEXPORT void Java_com_sample_helper_JUIHelper_JUICallbackHandler( 26 | JNIEnv *env, jobject thiz, int32_t id, int32_t message, int32_t param1, 27 | int32_t param2) { 28 | /* 29 | * id from Java is generated token from native side, which would be mapped back 30 | * into an UI pointer; so id is opaque to Java -- Java receives and returns back 31 | * the same id to native side 32 | */ 33 | JUIBase *p = JUIBase::id_factory_.getUIBase(id); 34 | if( p ) 35 | p->DispatchEvent(message, param1, param2); 36 | else { 37 | LOGE("Failed to get JUIBase pointer for %d in %s", id, __FILE__); 38 | } 39 | } 40 | } 41 | 42 | /* 43 | * IdFactory Implementation: our ID is just a simple integer starting from 1,and 44 | * keeps on counting up. There is no re-use -- even 45 | * one ID is removed from cache, we still counting up 46 | * It will take a long while to overflow a 32 bit integer 47 | * for UI ids. Even it does, it wrap back, at that time, 48 | * previous low values will long gone. 49 | */ 50 | int32_t IdFactory::cur_id_ = 0; 51 | int32_t IdFactory::getId(const JUIBase* ui_object) { 52 | auto it = ids_.find(ui_object); 53 | if (it == ids_.end()) { 54 | LOGE("IDs not inside hash for %p, patching done (%d)", ui_object, cur_id_); 55 | ids_[ui_object] = ++cur_id_; 56 | return cur_id_; 57 | } 58 | return it->second; 59 | } 60 | JUIBase* IdFactory::getUIBase(int32_t ui_id) { 61 | for (auto id: ids_) { 62 | if(id.second == ui_id) { 63 | return const_cast(id.first); 64 | } 65 | } 66 | LOGE("Unable to find object point for ID(%d, %x)", ui_id, ui_id); 67 | return nullptr; 68 | } 69 | bool IdFactory::insert(const JUIBase* ui_object) { 70 | bool status = true; 71 | auto it = ids_.find(ui_object); 72 | if (it != ids_.end()) { 73 | LOGE("Error: %p is already in = %d", it->first, it->second); 74 | LOGE("Overwriting %p with %p for it", it->first, ui_object); 75 | status = false; 76 | //return status; ---> let it fall through 77 | } 78 | ids_[ui_object] = ++cur_id_; 79 | return status; 80 | } 81 | bool IdFactory::remove(const JUIBase* ui_object) { 82 | auto it = ids_.find(ui_object); 83 | if (it == ids_.end()) { 84 | LOGE("IDs not inside hash for %p!!!", ui_object); 85 | return false; 86 | } 87 | ids_.erase(it); 88 | return true; 89 | } 90 | void IdFactory::debugDumpCurrentHashTable(void) { 91 | for (auto id: ids_) { 92 | LOGI("Cached ID (%p = %d)", id.first, id.second); 93 | } 94 | } 95 | 96 | /* 97 | * Our global instance for id factory 98 | */ 99 | IdFactory JUIBase::id_factory_; 100 | 101 | /* 102 | * JUIView 103 | */ 104 | // Attribute types for View 105 | std::unordered_map JUIView::map_attributes_; 106 | const AttributeType JUIView::attributes_[] = { 107 | {"AccessibilityLiveRegion", ATTRIBUTE_PARAMETER_INT}, 108 | {"Alpha", ATTRIBUTE_PARAMETER_FLOAT}, 109 | {"BackgroundResource", ATTRIBUTE_PARAMETER_INT}, 110 | {"Clickable", ATTRIBUTE_PARAMETER_BOOLEAN}, 111 | {"Enabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 112 | {"DrawingCacheQuality", ATTRIBUTE_PARAMETER_INT}, 113 | {"ScrollbarFadingEnabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 114 | {"FilterTouchesWhenObscured", ATTRIBUTE_PARAMETER_BOOLEAN}, 115 | {"FitsSystemWindows", ATTRIBUTE_PARAMETER_BOOLEAN}, 116 | {"Focusable", ATTRIBUTE_PARAMETER_BOOLEAN}, 117 | {"FocusableATTRIBUTE_PARAMETER_INTouchMode", ATTRIBUTE_PARAMETER_BOOLEAN}, 118 | {"HapticFeedbackEnabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 119 | {"Id", ATTRIBUTE_PARAMETER_INT}, 120 | {"ImportantForAccessibility", ATTRIBUTE_PARAMETER_INT}, 121 | {"ScrollContainer", ATTRIBUTE_PARAMETER_BOOLEAN}, 122 | {"KeepScreenOn", ATTRIBUTE_PARAMETER_BOOLEAN}, 123 | {"LayoutDirection", ATTRIBUTE_PARAMETER_INT}, 124 | {"LongClickable", ATTRIBUTE_PARAMETER_BOOLEAN}, 125 | {"MinimumHeight", ATTRIBUTE_PARAMETER_INT}, 126 | {"MinimumWidth", ATTRIBUTE_PARAMETER_INT}, 127 | {"NextFocusDownId", ATTRIBUTE_PARAMETER_INT}, 128 | {"NextFocusForwardId", ATTRIBUTE_PARAMETER_INT}, 129 | {"NextFocusLeftId", ATTRIBUTE_PARAMETER_INT}, 130 | {"NextFocusRightId", ATTRIBUTE_PARAMETER_INT}, 131 | {"NextFocusUpId", ATTRIBUTE_PARAMETER_INT}, 132 | {"PaddingRelative", ATTRIBUTE_PARAMETER_IIII}, 133 | {"Padding", ATTRIBUTE_PARAMETER_IIII}, 134 | {"VerticalFadingEdgeEnabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 135 | {"Rotation", ATTRIBUTE_PARAMETER_FLOAT}, 136 | {"RotationX", ATTRIBUTE_PARAMETER_FLOAT}, 137 | {"RotationY", ATTRIBUTE_PARAMETER_FLOAT}, 138 | {"SaveEnabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 139 | {"ScaleX", ATTRIBUTE_PARAMETER_FLOAT}, 140 | {"ScaleY", ATTRIBUTE_PARAMETER_FLOAT}, 141 | {"ScrollBarDefaultDelayBeforeFade", ATTRIBUTE_PARAMETER_INT}, 142 | {"ScrollBarFadeDuration", ATTRIBUTE_PARAMETER_INT}, 143 | {"ScrollBarSize", ATTRIBUTE_PARAMETER_INT}, 144 | {"ScrollBarStyle", ATTRIBUTE_PARAMETER_INT}, 145 | {"SoundEffectsEnabled", ATTRIBUTE_PARAMETER_BOOLEAN}, 146 | {"TextAlignment", ATTRIBUTE_PARAMETER_INT}, 147 | {"TextDirection", ATTRIBUTE_PARAMETER_INT}, 148 | {"PivotX", ATTRIBUTE_PARAMETER_FLOAT}, 149 | {"PivotY", ATTRIBUTE_PARAMETER_FLOAT}, 150 | {"TranslationX", ATTRIBUTE_PARAMETER_FLOAT}, 151 | {"TranslationY", ATTRIBUTE_PARAMETER_FLOAT}, 152 | {"Visibility", ATTRIBUTE_PARAMETER_INT}, 153 | }; 154 | 155 | JUIView::JUIView() 156 | : layoutWidth_(ATTRIBUTE_SIZE_WRAP_CONTENT), 157 | layoutHeight_(ATTRIBUTE_SIZE_WRAP_CONTENT), 158 | layoutWeight_(0.f), 159 | marginLeft_(0), 160 | marginRight_(0), 161 | marginTop_(0), 162 | marginBottom_(0) { 163 | // setup attribute map (once) 164 | if (map_attributes_.size() == 0) { 165 | for (int32_t i = 0; i < sizeof(attributes_) / sizeof(attributes_[0]); ++i) { 166 | map_attributes_[std::string(attributes_[i].attribute_name)] = 167 | attributes_[i].attribute_type; 168 | } 169 | } 170 | 171 | for (int32_t i = 0; i < LAYOUT_PARAMETER_COUNT; ++i) { 172 | array_current_rules_[i] = LAYOUT_PARAMETER_UNKNOWN; 173 | } 174 | } 175 | 176 | JUIView::~JUIView() { 177 | auto it = map_attribute_parameters.begin(); 178 | auto itEnd = map_attribute_parameters.end(); 179 | while (it != itEnd) { 180 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 181 | switch (p.type) { 182 | case ATTRIBUTE_PARAMETER_STRING: 183 | if (it->second.str != NULL) delete it->second.str; 184 | break; 185 | default: 186 | break; 187 | } 188 | it++; 189 | } 190 | } 191 | 192 | /* 193 | * Add relative layout rule to the view 194 | */ 195 | void JUIView::AddRule(const int32_t layoutParameterIndex, 196 | const int32_t parameter) { 197 | if (layoutParameterIndex < 0 || 198 | layoutParameterIndex >= LAYOUT_PARAMETER_COUNT) { 199 | LOGE("Invalid rule index"); 200 | return; 201 | } 202 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 203 | JUIWindow::GetHelperClassInstance(), "addRule", 204 | "(Landroid/view/View;II)V", GetJobject(), layoutParameterIndex, 205 | parameter); 206 | array_current_rules_[layoutParameterIndex] = parameter; 207 | } 208 | 209 | void JUIView::AddRule(const int32_t layoutParameterIndex, 210 | const JUIView *parameter) { 211 | 212 | /* 213 | * perform pointer to id xlation 214 | */ 215 | AddRule(layoutParameterIndex, id_factory_.getId(parameter)); 216 | } 217 | 218 | void JUIView::SetLayoutParams(const int32_t width, const int32_t height) { 219 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 220 | JUIWindow::GetHelperClassInstance(), "setLayoutParams", 221 | "(Landroid/view/View;II)V", GetJobject(), width, height); 222 | layoutWidth_ = width; 223 | layoutHeight_ = height; 224 | layoutWeight_ = 0.f; 225 | } 226 | 227 | void JUIView::SetLayoutParams(const int32_t width, const int32_t height, 228 | const float weight) { 229 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 230 | JUIWindow::GetHelperClassInstance(), "setLayoutParams", 231 | "(Landroid/view/View;IIF)V", GetJobject(), width, height, weight); 232 | layoutWidth_ = width; 233 | layoutHeight_ = height; 234 | layoutWeight_ = weight; 235 | } 236 | 237 | void JUIView::SetMargins(const int32_t left, const int32_t top, 238 | const int32_t right, const int32_t bottom) { 239 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 240 | JUIWindow::GetHelperClassInstance(), "setMargins", 241 | "(Landroid/view/View;IIII)V", GetJobject(), left, top, right, bottom); 242 | marginLeft_ = left; 243 | marginRight_ = right; 244 | marginTop_ = top; 245 | marginBottom_ = bottom; 246 | } 247 | 248 | void JUIView::RestoreParameters(std::unordered_map &map) { 249 | // Restore Layout Rule 250 | for (int32_t i = 0; i < LAYOUT_PARAMETER_COUNT; ++i) { 251 | if (array_current_rules_[i] != LAYOUT_PARAMETER_UNKNOWN) { 252 | AddRule(i, array_current_rules_[i]); 253 | } 254 | } 255 | 256 | auto it = map_attribute_parameters.begin(); 257 | auto itEnd = map_attribute_parameters.end(); 258 | while (it != itEnd) { 259 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 260 | switch (p.type) { 261 | case ATTRIBUTE_PARAMETER_INT: 262 | JUIBase::SetAttribute(map, it->first.c_str(), (int32_t)p.i); 263 | break; 264 | case ATTRIBUTE_PARAMETER_FLOAT: 265 | JUIBase::SetAttribute(map, it->first.c_str(), p.f); 266 | break; 267 | case ATTRIBUTE_PARAMETER_BOOLEAN: 268 | JUIBase::SetAttribute(map, it->first.c_str(), p.f); 269 | break; 270 | case ATTRIBUTE_PARAMETER_STRING: 271 | JUIBase::SetAttribute(map, it->first.c_str(), p.str->c_str()); 272 | break; 273 | case ATTRIBUTE_PARAMETER_IF: 274 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_if.i1, 275 | p.param_if.f2); 276 | break; 277 | case ATTRIBUTE_PARAMETER_FF: 278 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_ff.f1, 279 | p.param_ff.f2); 280 | break; 281 | case ATTRIBUTE_PARAMETER_IIII: 282 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_iiii.i1, 283 | p.param_iiii.i2, p.param_iiii.i3, 284 | p.param_iiii.i4); 285 | break; 286 | case ATTRIBUTE_PARAMETER_FFFI: 287 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_fffi.f1, 288 | p.param_fffi.f2, p.param_fffi.f3, p.param_fffi.i); 289 | break; 290 | default: 291 | break; 292 | } 293 | it++; 294 | } 295 | 296 | if (layoutWidth_ != ATTRIBUTE_SIZE_WRAP_CONTENT || 297 | layoutHeight_ != ATTRIBUTE_SIZE_WRAP_CONTENT || layoutWeight_ != 0.f) { 298 | if (layoutWeight_ != 0.f) 299 | SetLayoutParams(layoutWidth_, layoutHeight_, layoutWeight_); 300 | else 301 | SetLayoutParams(layoutWidth_, layoutHeight_); 302 | } 303 | 304 | if (marginLeft_ || marginRight_ || marginTop_ || marginBottom_) { 305 | SetMargins(marginLeft_, marginTop_, marginRight_, marginBottom_); 306 | } 307 | } 308 | 309 | std::unordered_map JUITextView::map_attributes_; 310 | const AttributeType JUITextView::attributes_[] = { 311 | {"AutoLinkMask", ATTRIBUTE_PARAMETER_INT}, 312 | {"Text", ATTRIBUTE_PARAMETER_STRING}, 313 | {"CursorVisible", ATTRIBUTE_PARAMETER_BOOLEAN}, 314 | {"CompoundDrawablesWithIntrinsicBounds", ATTRIBUTE_PARAMETER_IIII}, 315 | {"CompoundDrawablesRelativeWithIntrinsicBounds", ATTRIBUTE_PARAMETER_IIII}, 316 | {"CompoundDrawablePadding", ATTRIBUTE_PARAMETER_INT}, 317 | {"InputExtras", ATTRIBUTE_PARAMETER_INT}, 318 | {"Ellipsize", ATTRIBUTE_PARAMETER_INT /*TextUtils.TruncateAt*/}, 319 | {"Ems", ATTRIBUTE_PARAMETER_INT}, 320 | {"Typeface", ATTRIBUTE_PARAMETER_INT /*Typeface*/}, 321 | {"FreezesText", ATTRIBUTE_PARAMETER_BOOLEAN}, 322 | {"Gravity", ATTRIBUTE_PARAMETER_INT}, 323 | {"Height", ATTRIBUTE_PARAMETER_INT}, 324 | {"Hint", ATTRIBUTE_PARAMETER_INT}, 325 | {"ImeOptions", ATTRIBUTE_PARAMETER_INT}, 326 | {"IncludeFontPadding", ATTRIBUTE_PARAMETER_BOOLEAN}, 327 | {"RawInputType", ATTRIBUTE_PARAMETER_INT}, 328 | {"LineSpacing", ATTRIBUTE_PARAMETER_FF}, 329 | {"Lines", ATTRIBUTE_PARAMETER_INT}, 330 | {"LinksClickable", ATTRIBUTE_PARAMETER_BOOLEAN}, 331 | {"MarqueeRepeatLimit", ATTRIBUTE_PARAMETER_INT}, 332 | {"MaxEms", ATTRIBUTE_PARAMETER_INT}, 333 | {"MaxHeight", ATTRIBUTE_PARAMETER_INT}, 334 | {"MaxLines", ATTRIBUTE_PARAMETER_INT}, 335 | {"MaxWidth", ATTRIBUTE_PARAMETER_INT}, 336 | {"MinEms", ATTRIBUTE_PARAMETER_INT}, 337 | {"MinHeight", ATTRIBUTE_PARAMETER_INT}, 338 | {"MinLines", ATTRIBUTE_PARAMETER_INT}, 339 | {"MinWidth", ATTRIBUTE_PARAMETER_INT}, 340 | {"PrivateImeOptions", ATTRIBUTE_PARAMETER_STRING}, 341 | {"HorizontallyScrolling", ATTRIBUTE_PARAMETER_BOOLEAN}, 342 | {"SelectAllOnFocus", ATTRIBUTE_PARAMETER_BOOLEAN}, 343 | {"ShadowLayer", ATTRIBUTE_PARAMETER_FFFI}, 344 | {"AllCaps", ATTRIBUTE_PARAMETER_BOOLEAN}, 345 | {"TextColor", ATTRIBUTE_PARAMETER_INT}, 346 | {"HighlightColor", ATTRIBUTE_PARAMETER_INT}, 347 | {"HintTextColor", ATTRIBUTE_PARAMETER_INT}, 348 | {"LinkTextColor", ATTRIBUTE_PARAMETER_INT}, 349 | {"TextScaleX", ATTRIBUTE_PARAMETER_FLOAT}, 350 | {"TextSize", ATTRIBUTE_PARAMETER_IF}, 351 | {"Width", ATTRIBUTE_PARAMETER_INT}}; 352 | 353 | JUITextView::JUITextView() : JUIView() { 354 | obj_ = JUIWindow::GetInstance()->CreateWidget("JUITextView", this); 355 | if (obj_ == NULL) LOGI("Class initialization failure"); 356 | 357 | Init(); 358 | } 359 | 360 | JUITextView::JUITextView(const char *str) : JUIView() { 361 | obj_ = JUIWindow::GetInstance()->CreateWidget("JUITextView", this); 362 | if (obj_ == NULL) LOGI("Class initialization failure"); 363 | 364 | Init(); 365 | SetAttribute("Text", str); 366 | } 367 | 368 | JUITextView::JUITextView(const bool b) : JUIView() { 369 | if (b == true) 370 | JUITextView(); 371 | else 372 | Init(); 373 | } 374 | 375 | void JUITextView::Init() { 376 | // setup attribute map (once) 377 | if (map_attributes_.size() == 0) { 378 | // Add base class's map 379 | map_attributes_.insert(JUIView::map_attributes_.begin(), 380 | JUIView::map_attributes_.end()); 381 | 382 | for (int32_t i = 0; i < sizeof(attributes_) / sizeof(attributes_[0]); ++i) { 383 | map_attributes_[std::string(attributes_[i].attribute_name)] = 384 | attributes_[i].attribute_type; 385 | } 386 | } 387 | } 388 | 389 | JUITextView::~JUITextView() { 390 | if (obj_ != NULL) { 391 | jui_helper::JUIWindow::GetInstance()->CloseWidget(obj_); 392 | obj_ = NULL; 393 | } 394 | } 395 | 396 | void JUITextView::Restore() { 397 | // Recreate Java Widget when the activity has been disposed 398 | obj_ = JUIWindow::GetInstance()->CreateWidget("JUITextView", this); 399 | if (obj_ == NULL) LOGI("Class initialization failure"); 400 | 401 | RestoreParameters(map_attributes_); 402 | } 403 | 404 | /* 405 | * Button 406 | */ 407 | JUIButton::JUIButton() : JUITextView(false), onclick_callback_(NULL) { Init(); } 408 | 409 | JUIButton::JUIButton(const char *str) : JUITextView(false) { 410 | Init(); 411 | SetAttribute("Text", str); 412 | } 413 | 414 | JUIButton::JUIButton(const bool b) : JUITextView(false) { 415 | if (b == true) JUIButton(); 416 | } 417 | 418 | void JUIButton::Init() { 419 | obj_ = JUIWindow::GetInstance()->CreateWidget("JUIButton", this); 420 | if (obj_ == NULL) LOGI("Class initialization failure"); 421 | } 422 | 423 | JUIButton::~JUIButton() { 424 | if (obj_ != NULL) { 425 | jui_helper::JUIWindow::GetInstance()->CloseWidget(obj_); 426 | obj_ = NULL; 427 | } 428 | } 429 | 430 | void JUIButton::Restore() { 431 | // Recreate Java Widget when the activity has been disposed 432 | obj_ = JUIWindow::GetInstance()->CreateWidget("JUIButton", this); 433 | if (obj_ == NULL) LOGI("Class initialization failure"); 434 | 435 | RestoreParameters(map_attributes_); 436 | } 437 | 438 | void JUIButton::DispatchEvent(const int32_t message, const int32_t param1, 439 | const int32_t param2) { 440 | if (onclick_callback_ != NULL) onclick_callback_(this, message); 441 | } 442 | 443 | bool JUIButton::SetCallback( 444 | std::function callback) { 445 | bool b = true; 446 | onclick_callback_ = callback; 447 | return b; 448 | } 449 | 450 | } // namespace jui_helper 451 | 452 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/JavaUI.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_H_ 18 | #define _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_H_ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "JNIHelper.h" 30 | #include "JavaUI_View.h" 31 | 32 | namespace jui_helper { 33 | /****************************************************************** 34 | * jui_helper is a helper library to use Java UI easily from Native code. 35 | * With the helper, an application can instantiate Java controls such as Button, 36 | * CheckBox, 37 | * SeekBar from Native code and put it over NativeActivity. Also you can 38 | * register a native 39 | * callback to the controls. 40 | * For a sample implementation how to use jui_helper, refer JavaUI sample. 41 | * 42 | * NOTE: To use Dialog, Android Support library is required. 43 | * Setup support library: 44 | * http://developer.android.com/tools/support-library/setup.html 45 | */ 46 | 47 | /****************************************************************** 48 | * Helper class to access Java UI from native code 49 | */ 50 | 51 | /* 52 | * Forward decls 53 | */ 54 | class JUIView; 55 | class JUIWindow; 56 | class JUIDialog; 57 | 58 | //------------------------------------------------- 59 | // JUITextView 60 | //------------------------------------------------- 61 | class JUITextView : public JUIView { 62 | public: 63 | JUITextView(); 64 | explicit JUITextView(const char *str); 65 | virtual ~JUITextView(); 66 | 67 | /* 68 | * Set attributes to the component 69 | * For a list of attributes, refer attributes_ array 70 | */ 71 | template 72 | bool SetAttribute(const char *strAttribute, const T t) { 73 | return JUIBase::SetAttribute(map_attributes_, strAttribute, t); 74 | } 75 | 76 | bool SetAttribute(const char *strAttribute, const char *str) { 77 | return JUIBase::SetAttribute(map_attributes_, strAttribute, str); 78 | } 79 | 80 | template 81 | bool SetAttribute(const char *strAttribute, T t, T2 t2) { 82 | return JUIBase::SetAttribute(map_attributes_, strAttribute, t, t2); 83 | } 84 | 85 | template 86 | bool SetAttribute(const char *strAttribute, T p1, T2 p2, T3 p3, T4 p4) { 87 | return JUIBase::SetAttribute(map_attributes_, strAttribute, p1, p2, p3, p4); 88 | } 89 | 90 | /* 91 | * Retrieve attribute of the widget 92 | */ 93 | template 94 | bool GetAttributeA(const char *strAttribute, T *value) { 95 | return JUIView::GetAttribute(map_attributes_, strAttribute, value); 96 | } 97 | 98 | private: 99 | const static AttributeType attributes_[]; 100 | void Init(); 101 | 102 | protected: 103 | static std::unordered_map map_attributes_; 104 | virtual void Restore(); 105 | 106 | explicit JUITextView(const bool b); 107 | }; 108 | 109 | /* 110 | * JUIButton 111 | */ 112 | class JUIButton : public JUITextView { 113 | public: 114 | JUIButton(); 115 | explicit JUIButton(const char *str); 116 | virtual ~JUIButton(); 117 | 118 | /* 119 | * Dispatch Widget events. This one is called from Java code through 120 | * Java_com_sample_helper_JUIHelper_JUICallbackHandler() 121 | */ 122 | virtual void DispatchEvent(const int32_t message, const int32_t param1, 123 | const int32_t param2); 124 | 125 | /* 126 | * Set callback to an input event 127 | */ 128 | bool SetCallback( 129 | std::function callback); 130 | 131 | private: 132 | void Init(); 133 | std::function onclick_callback_; 134 | 135 | protected: 136 | explicit JUIButton(const bool b); 137 | virtual void Restore(); 138 | }; 139 | 140 | /* 141 | * JUIWindow represents a popup window with a relative layout put on the window 142 | * An application can create JUIView based classes and add them to JUIWindow to 143 | * show Java Widget over 144 | * native activity. 145 | */ 146 | class JUIWindow { 147 | /* 148 | * These classes need to be a friend class to access protected methods 149 | */ 150 | friend class JUIView; 151 | friend class JUITextView; 152 | friend class JUIButton; 153 | friend class JUIDialog; 154 | 155 | public: 156 | /* 157 | * Retrieve the singleton object of the helper. 158 | * Static member of the class 159 | 160 | * Methods in the class are NOT designed as thread safe. 161 | */ 162 | static JUIWindow *GetInstance(); 163 | 164 | /* 165 | * Retrieve an instance of JUIHelper Java class 166 | * 167 | */ 168 | static jobject GetHelperClassInstance(); 169 | 170 | /* 171 | * Retrieve JUIHelper Java class 172 | * 173 | */ 174 | static jclass GetHelperClass(); 175 | 176 | /* 177 | * Initialize window with an activity 178 | */ 179 | static void Init(ANativeActivity *activity, 180 | const char *helper_class_name = NULL); 181 | 182 | /* 183 | * Close the window 184 | */ 185 | void Close(); 186 | 187 | /* 188 | * Resume JUIWindow 189 | * This function needs to be invoked corresponding activity life cycle event 190 | */ 191 | void Resume(ANativeActivity *activity, const int32_t command); 192 | 193 | /* 194 | * Suspend JUIWindow 195 | * This function needs to be invoked corresponding activity life cycle event 196 | */ 197 | void Suspend(const int32_t command); 198 | 199 | /* 200 | * Add JUIView classes to the window 201 | */ 202 | void AddView(JUIView *view); 203 | 204 | /* 205 | * Get context associated with the window class 206 | */ 207 | jobject GetContext() { 208 | if (activity_ == NULL) return NULL; 209 | return activity_->clazz; 210 | } 211 | 212 | /* 213 | * Get mutex for JUI helpers 214 | */ 215 | std::mutex &GetMutex() { return mutex_; } 216 | 217 | private: 218 | JUIWindow(); 219 | ~JUIWindow(); 220 | JUIWindow(const JUIWindow &rhs); 221 | JUIWindow &operator=(const JUIWindow &rhs); 222 | 223 | ANativeActivity *activity_; 224 | jobject popupWindow_; 225 | 226 | std::vector views_; 227 | 228 | bool suspended_; 229 | bool windowDestroyed_; 230 | 231 | jobject jni_helper_java_ref_; 232 | jclass jni_helper_java_class_; 233 | 234 | JUIDialog *dialog_; 235 | 236 | // mutex for synchronization 237 | mutable std::mutex mutex_; 238 | 239 | jobject CreateWidget(const char *strWidgetName, void *id); 240 | jobject CreateWidget(const char *strWidgetName, void *id, 241 | const int32_t param); 242 | void CloseWidget(jobject obj); 243 | 244 | void SetDialog(JUIDialog *dialog) { dialog_ = dialog; } 245 | }; 246 | 247 | /* 248 | * JUIDialog represents a dialog framgent 249 | * An application can create JUIView based classes and add them to JUIDialog to 250 | * show Java Widget over 251 | * native activity. 252 | */ 253 | class JUIDialog : public JUIBase { 254 | /* 255 | * These classes need to be a friend class to access protected methods 256 | */ 257 | friend class JUIView; 258 | friend class JUITextView; 259 | friend class JUIButton; 260 | friend class JUIWindow; 261 | 262 | public: 263 | JUIDialog(); 264 | explicit JUIDialog(ANativeActivity *activity); 265 | virtual ~JUIDialog(); 266 | 267 | /* 268 | * Initialize window with an activity 269 | */ 270 | void Init(ANativeActivity *activity); 271 | 272 | /* 273 | * Close the dialog 274 | */ 275 | void Close(); 276 | 277 | /* 278 | * Show dialog 279 | */ 280 | void Show(); 281 | 282 | /* 283 | * Add JUIView classes to the dialog 284 | */ 285 | void AddView(JUIView *view); 286 | 287 | /* 288 | * Set a callback to dialog life cycle event 289 | */ 290 | bool SetCallback(const int32_t message, 291 | std::function callback); 293 | 294 | /* 295 | * Dispatch Widget events. This one is called from Java code through 296 | * Java_com_sample_helper_JUIHelper_JUICallbackHandler() 297 | */ 298 | virtual void DispatchEvent(const int32_t message, const int32_t param1, 299 | const int32_t param2); 300 | 301 | /* 302 | * Set attributes to the component 303 | * For a list of attributes, refer attributes_ array 304 | */ 305 | template 306 | bool SetAttribute(const char *strAttribute, const T t) { 307 | return JUIBase::SetAttribute(map_attributes_, strAttribute, t); 308 | } 309 | 310 | bool SetAttribute(const char *strAttribute, const char *str) { 311 | return JUIBase::SetAttribute(map_attributes_, strAttribute, str); 312 | } 313 | 314 | protected: 315 | static const AttributeType attributes_[]; 316 | static std::unordered_map map_attributes_; 317 | 318 | ANativeActivity *activity_; 319 | std::vector views_; 320 | bool suspended_; 321 | 322 | std::function 323 | dismiss_callback_; 324 | std::function 325 | cancel_callback_; 326 | 327 | void CreateDialog(); 328 | void DeleteObject(); 329 | virtual void Suspend(); 330 | virtual void Resume(ANativeActivity *activity); 331 | void RestoreParameters(std::unordered_map &map); 332 | }; 333 | } // namespace ndkHelper 334 | #endif // _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_H_ 335 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/JavaUI_Dialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "JavaUI.h" 17 | 18 | namespace jui_helper { 19 | /* 20 | * JUI Dialog 21 | */ 22 | std::unordered_map JUIDialog::map_attributes_; 23 | const AttributeType JUIDialog::attributes_[] = { 24 | {"Title", ATTRIBUTE_PARAMETER_STRING}, 25 | }; 26 | 27 | JUIDialog::JUIDialog() 28 | : activity_(NULL), 29 | suspended_(false), 30 | dismiss_callback_(NULL), 31 | cancel_callback_(NULL) { 32 | map_attribute_parameters.clear(); 33 | } 34 | 35 | JUIDialog::JUIDialog(ANativeActivity *activity) 36 | : suspended_(false), dismiss_callback_(NULL), cancel_callback_(NULL) { 37 | Init(activity); 38 | } 39 | 40 | JUIDialog::~JUIDialog() { 41 | Close(); 42 | 43 | auto it = map_attribute_parameters.begin(); 44 | auto itEnd = map_attribute_parameters.end(); 45 | while (it != itEnd) { 46 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 47 | switch (p.type) { 48 | case ATTRIBUTE_PARAMETER_STRING: 49 | if (it->second.str != NULL) delete it->second.str; 50 | break; 51 | default: 52 | break; 53 | } 54 | it++; 55 | } 56 | } 57 | 58 | /* 59 | * Init 60 | */ 61 | void JUIDialog::Init(ANativeActivity *activity) { 62 | // setup attribute map (once) 63 | if (map_attributes_.size() == 0) { 64 | for (int32_t i = 0; i < sizeof(attributes_) / sizeof(attributes_[0]); ++i) { 65 | map_attributes_[std::string(attributes_[i].attribute_name)] = 66 | attributes_[i].attribute_type; 67 | } 68 | } 69 | 70 | activity_ = activity; 71 | CreateDialog(); 72 | } 73 | 74 | void JUIDialog::CreateDialog() { 75 | ndk_helper::JNIHelper &helper = *ndk_helper::JNIHelper::GetInstance(); 76 | JNIEnv *env = helper.AttachCurrentThread(); 77 | 78 | // Create dialog 79 | jmethodID mid = env->GetMethodID( 80 | JUIWindow::GetInstance()->GetHelperClass(), "createDialog", 81 | "(Landroid/app/NativeActivity;)Ljava/lang/Object;"); 82 | jobject obj = 83 | env->CallObjectMethod(JUIWindow::GetInstance()->GetHelperClassInstance(), 84 | mid, activity_->clazz); 85 | if (obj == NULL) { 86 | LOGI("Failed creating Dialog object"); 87 | } 88 | obj_ = env->NewGlobalRef(obj); 89 | 90 | // Notify 'id' to JNI side 91 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod(obj_, "setID", "(I)V", 92 | id_factory_.getId(this)); 93 | env->DeleteLocalRef(obj); 94 | } 95 | 96 | void JUIDialog::DeleteObject() { 97 | if (obj_) { 98 | ndk_helper::JNIHelper *helper = ndk_helper::JNIHelper::GetInstance(); 99 | JNIEnv *env = helper->AttachCurrentThread(); 100 | env->DeleteGlobalRef(obj_); 101 | obj_ = NULL; 102 | } 103 | } 104 | 105 | /* 106 | * Close 107 | */ 108 | void JUIDialog::Close() { 109 | if (obj_) { 110 | LOGI("Closing Dialog"); 111 | 112 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod(obj_, "dismiss", 113 | "()V"); 114 | 115 | // Delete child views 116 | auto itBegin = views_.begin(); 117 | auto itEnd = views_.end(); 118 | while (itBegin != itEnd) { 119 | delete *itBegin; 120 | itBegin++; 121 | } 122 | 123 | DeleteObject(); 124 | views_.clear(); 125 | activity_ = NULL; 126 | obj_ = NULL; 127 | jui_helper::JUIWindow::GetInstance()->SetDialog(NULL); 128 | } 129 | } 130 | 131 | /* 132 | * Add JUIView to popup window 133 | */ 134 | void JUIDialog::AddView(JUIView *view) { 135 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 136 | obj_, "addView", "(Landroid/view/View;)V", view->GetJobject()); 137 | views_.push_back(view); 138 | } 139 | 140 | void JUIDialog::Show() { 141 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod(obj_, "show", "()V"); 142 | jui_helper::JUIWindow::GetInstance()->SetDialog(this); 143 | } 144 | 145 | void JUIDialog::Suspend() { 146 | // Close existing dialog 147 | DeleteObject(); 148 | LOGI("Suspending Dialog"); 149 | suspended_ = true; 150 | } 151 | 152 | void JUIDialog::Resume(ANativeActivity *activity) { 153 | LOGI("Resuming Dialog"); 154 | activity_ = activity; 155 | DeleteObject(); 156 | 157 | ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this]() { 158 | suspended_ = false; 159 | // Creating dialog asynchronous to avoid a crash inside a framework 160 | CreateDialog(); 161 | RestoreParameters(map_attributes_); 162 | 163 | // Restore widgets 164 | auto itBegin = views_.begin(); 165 | auto itEnd = views_.end(); 166 | while (itBegin != itEnd) { 167 | // Restore 168 | (*itBegin)->Restore(); 169 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 170 | obj_, "addView", "(Landroid/view/View;)V", (*itBegin)->GetJobject()); 171 | 172 | itBegin++; 173 | } 174 | 175 | Show(); // Show the dialog again 176 | }); 177 | } 178 | 179 | void JUIDialog::DispatchEvent(const int32_t message, const int32_t param1, 180 | const int32_t param2) { 181 | switch (message) { 182 | case JUICALLBACK_DIALOG_DISMISSED: 183 | if (suspended_ == false) { 184 | jui_helper::JUIWindow::GetInstance()->SetDialog(NULL); 185 | if (dismiss_callback_) dismiss_callback_(this, message); 186 | } 187 | break; 188 | case JUICALLBACK_DIALOG_CANCELLED: 189 | if (suspended_ == false) { 190 | jui_helper::JUIWindow::GetInstance()->SetDialog(NULL); 191 | if (cancel_callback_) cancel_callback_(this, message); 192 | } 193 | break; 194 | default: 195 | break; 196 | } 197 | } 198 | 199 | bool JUIDialog::SetCallback( 200 | const int32_t message, 201 | std::function 202 | callback) { 203 | switch (message) { 204 | case JUICALLBACK_DIALOG_DISMISSED: 205 | dismiss_callback_ = callback; 206 | break; 207 | case JUICALLBACK_DIALOG_CANCELLED: 208 | cancel_callback_ = callback; 209 | break; 210 | default: 211 | break; 212 | } 213 | return true; 214 | } 215 | 216 | void JUIDialog::RestoreParameters( 217 | std::unordered_map &map) { 218 | auto it = map_attribute_parameters.begin(); 219 | auto itEnd = map_attribute_parameters.end(); 220 | while (it != itEnd) { 221 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 222 | switch (p.type) { 223 | case ATTRIBUTE_PARAMETER_INT: 224 | JUIBase::SetAttribute(map, it->first.c_str(), (int32_t)p.i); 225 | break; 226 | case ATTRIBUTE_PARAMETER_FLOAT: 227 | JUIBase::SetAttribute(map, it->first.c_str(), p.f); 228 | break; 229 | case ATTRIBUTE_PARAMETER_BOOLEAN: 230 | JUIBase::SetAttribute(map, it->first.c_str(), p.f); 231 | break; 232 | case ATTRIBUTE_PARAMETER_STRING: 233 | JUIBase::SetAttribute(map, it->first.c_str(), p.str->c_str()); 234 | break; 235 | case ATTRIBUTE_PARAMETER_IF: 236 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_if.i1, 237 | p.param_if.f2); 238 | break; 239 | case ATTRIBUTE_PARAMETER_FF: 240 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_ff.f1, 241 | p.param_ff.f2); 242 | break; 243 | case ATTRIBUTE_PARAMETER_IIII: 244 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_iiii.i1, 245 | p.param_iiii.i2, p.param_iiii.i3, 246 | p.param_iiii.i4); 247 | break; 248 | case ATTRIBUTE_PARAMETER_FFFI: 249 | JUIBase::SetAttribute(map, it->first.c_str(), p.param_fffi.f1, 250 | p.param_fffi.f2, p.param_fffi.f3, p.param_fffi.i); 251 | break; 252 | default: 253 | break; 254 | } 255 | it++; 256 | } 257 | } 258 | } // namespace jui_helper 259 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/JavaUI_View.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_VIEW_H_ 18 | #define _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_VIEW_H_ 19 | 20 | namespace jui_helper { 21 | 22 | /* 23 | * Layout parameters for AddRule() call. 24 | * Similar behavior to Java RelativeLayout definitions 25 | */ 26 | enum LayoutParameterType { 27 | LAYOUT_PARAMETER_UNKNOWN = -2, 28 | LAYOUT_PARAMETER_TRUE = -1, 29 | LAYOUT_PARAMETER_LEFT_OF = 0, 30 | LAYOUT_PARAMETER_RIGHT_OF = 1, 31 | LAYOUT_PARAMETER_ABOVE = 2, 32 | LAYOUT_PARAMETER_BELOW = 3, 33 | LAYOUT_PARAMETER_ALIGN_BASELINE = 4, 34 | LAYOUT_PARAMETER_ALIGN_LEFT = 5, 35 | LAYOUT_PARAMETER_ALIGN_TOP = 6, 36 | LAYOUT_PARAMETER_ALIGN_RIGHT = 7, 37 | LAYOUT_PARAMETER_ALIGN_BOTTOM = 8, 38 | LAYOUT_PARAMETER_ALIGN_PARENT_LEFT = 9, 39 | LAYOUT_PARAMETER_ALIGN_PARENT_TOP = 10, 40 | LAYOUT_PARAMETER_ALIGN_PARENT_RIGHT = 11, 41 | LAYOUT_PARAMETER_ALIGN_PARENT_BOTTOM = 12, 42 | LAYOUT_PARAMETER_CENTER_IN_PARENT = 13, 43 | LAYOUT_PARAMETER_CENTER_HORIZONTAL = 14, 44 | LAYOUT_PARAMETER_CENTER_VERTICAL = 15, 45 | LAYOUT_PARAMETER_START_OF = 16, 46 | LAYOUT_PARAMETER_END_OF = 17, 47 | LAYOUT_PARAMETER_ALIGN_START = 18, 48 | LAYOUT_PARAMETER_ALIGN_END = 19, 49 | LAYOUT_PARAMETER_ALIGN_PARENT_START = 20, 50 | LAYOUT_PARAMETER_ALIGN_PARENT_END = 21, 51 | LAYOUT_PARAMETER_COUNT = 22, 52 | }; 53 | 54 | /* 55 | * Callback type for event callbacks in widgets. 56 | * Parameter for SetCallback() call. 57 | */ 58 | enum JUICallbackType { 59 | JUICALLBACK_SEEKBAR_STOP_TRACKING_TOUCH = 1, 60 | JUICALLBACK_SEEKBAR_START_TRACKING_TOUCH = 2, 61 | JUICALLBACK_SEEKBAR_PROGRESSCHANGED = 3, 62 | JUICALLBACK_COMPOUNDBUTTON_CHECKED = 4, 63 | JUICALLBACK_BUTTON_DOWN = 5, 64 | JUICALLBACK_BUTTON_UP = 6, 65 | JUICALLBACK_BUTTON_CANCELED = 7, 66 | 67 | JUICALLBACK_DIALOG_DISMISSED = 108, 68 | JUICALLBACK_DIALOG_CANCELLED = 109, 69 | }; 70 | 71 | /* 72 | * Gravity attribute settings 73 | */ 74 | enum AttributeGravityType { 75 | ATTRIBUTE_GRAVITY_TOP = 0x30, 76 | ATTRIBUTE_GRAVITY_BOTTOM = 0x50, 77 | ATTRIBUTE_GRAVITY_LEFT = 0x03, 78 | ATTRIBUTE_GRAVITY_RIGHT = 0x05, 79 | ATTRIBUTE_GRAVITY_CENTER_VERTICAL = 0x10, 80 | ATTRIBUTE_GRAVITY_FILL_VERTICAL = 0x70, 81 | ATTRIBUTE_GRAVITY_CENTER_HORIZONTAL = 0x01, 82 | ATTRIBUTE_GRAVITY_FILL_HORIZONTAL = 0x07, 83 | ATTRIBUTE_GRAVITY_CENTER = 0x11, 84 | ATTRIBUTE_GRAVITY_FILL = 0x77, 85 | ATTRIBUTE_GRAVITY_CLIP_VERTICAL = 0x80, 86 | ATTRIBUTE_GRAVITY_CLIP_HORIZONTAL = 0x08, 87 | ATTRIBUTE_GRAVITY_START = 0x00800003, 88 | ATTRIBUTE_GRAVITY_END = 0x00800005, 89 | }; 90 | 91 | /* 92 | * Attribute unit for SetAttribute() parameter 93 | */ 94 | enum AttributeUnitType { 95 | ATTRIBUTE_UNIT_PX = 0x0, 96 | ATTRIBUTE_UNIT_DIP = 0x1, 97 | ATTRIBUTE_UNIT_SP = 0x2, 98 | ATTRIBUTE_UNIT_PT = 0x3, 99 | ATTRIBUTE_UNIT_IN = 0x4, 100 | ATTRIBUTE_UNIT_MM = 0x5, 101 | }; 102 | 103 | /* 104 | * Size attribute for AddRule() 105 | */ 106 | enum AttributeSizeType { 107 | ATTRIBUTE_SIZE_MATCH_PARENT = -1, 108 | ATTRIBUTE_SIZE_WRAP_CONTENT = -2, 109 | }; 110 | 111 | /* 112 | * Linear layout orientation 113 | */ 114 | enum LayoutOrientationType { 115 | LAYOUT_ORIENTATION_HORIZONTAL = 0, 116 | LAYOUT_ORIENTATION_VERTICAL = 1, 117 | }; 118 | 119 | /* 120 | * Enum for alert dialog button 121 | */ 122 | enum AlertDialogButtonType { 123 | ALERTDIALOG_BUTTON_NEGATIVE = -2, 124 | ALERTDIALOG_BUTTON_NEUTRAL = -3, 125 | ALERTDIALOG_BUTTON_POSITIVE = -1, 126 | }; 127 | 128 | /* 129 | * ProgressBar style 130 | */ 131 | enum ProgressBarStyleType { 132 | PROGRESS_BAR_STYLE_DEFAULT = 0x01010077, 133 | PROGRESS_BAR_STYLE_HIROZONTAL = 0x01010078, 134 | PROGRESS_BAR_STYLE_SMALL = 0x01010079, 135 | PROGRESS_BAR_STYLE_LARGE = 0x101007A, 136 | PROGRESS_BAR_STYLE_INVERSE = 0x01010287, 137 | PROGRESS_BAR_STYLE_SMALL_INVERSE = 0x01010288, 138 | PROGRESS_BAR_STYLE_LARGE_INVARSE = 0x01010289, 139 | PROGRESS_BAR_STYLE_SMALL_TITLE = 0x0101020f, 140 | }; 141 | 142 | enum ViewVisibility { 143 | VIEW_VISIVILITY_VISIBLE = 0, 144 | VIEW_VISIVILITY_INVISIBLE = 4, 145 | VIEW_VISIVILITY_GONE = 8, 146 | }; 147 | 148 | /* 149 | * Internal enums for attribute parameter type 150 | */ 151 | enum AttributeParapeterType { 152 | ATTRIBUTE_PARAMETER_INT, 153 | ATTRIBUTE_PARAMETER_FLOAT, 154 | ATTRIBUTE_PARAMETER_BOOLEAN, 155 | ATTRIBUTE_PARAMETER_STRING, 156 | ATTRIBUTE_PARAMETER_IF, // parameters of int32_t, float 157 | ATTRIBUTE_PARAMETER_FF, // parameters of 2 floats 158 | ATTRIBUTE_PARAMETER_III, // parameters of int32_t, int32_t, int32_t 159 | ATTRIBUTE_PARAMETER_IIII, // parameters of int32_t, int32_t, int32_t, int32_t 160 | ATTRIBUTE_PARAMETER_FFFI, // parameters of float, float, float, int32_t 161 | }; 162 | 163 | /* 164 | * Internal structure to store attribute parameters 165 | */ 166 | struct AttributeParameterStore { 167 | AttributeParapeterType type; 168 | union { 169 | int32_t i; 170 | float f; 171 | bool b; 172 | std::string *str; 173 | struct { 174 | int32_t i1; 175 | float f2; 176 | } param_if; 177 | struct { 178 | float f1; 179 | float f2; 180 | } param_ff; 181 | struct { 182 | int32_t i1; 183 | int32_t i2; 184 | int32_t i3; 185 | } param_iii; 186 | struct { 187 | float f1; 188 | float f2; 189 | float f3; 190 | int32_t i; 191 | } param_fffi; 192 | struct { 193 | int32_t i1; 194 | int32_t i2; 195 | int32_t i3; 196 | int32_t i4; 197 | } param_iiii; 198 | }; 199 | }; 200 | 201 | struct AttributeType { 202 | const char *attribute_name; 203 | const int32_t attribute_type; 204 | }; 205 | 206 | /* 207 | * class IdFactory: cache UI pointers and generate an ID, send ID to Java side 208 | * when get id back from Java, retrieve the UI pointer for that 209 | * ID, and continue. Mainly purpose is for things to be called 210 | * back on UI thread 211 | */ 212 | class JUIBase; 213 | class IdFactory { 214 | public: 215 | int32_t getId(const JUIBase* ui_object); 216 | JUIBase* getUIBase(int32_t ui_id); 217 | void debugDumpCurrentHashTable(void); 218 | bool insert(const JUIBase* ui_object); 219 | bool remove(const JUIBase* ui_object); 220 | 221 | private: 222 | std::unordered_map ids_; 223 | static int32_t cur_id_; 224 | }; 225 | 226 | /* 227 | * Base class of JUIView 228 | */ 229 | class JUIBase { 230 | public: 231 | JUIBase() : obj_(NULL) { id_factory_.insert(this); } 232 | virtual ~JUIBase() { id_factory_.remove(this); } 233 | 234 | /* 235 | * Dispatch Widget events. This one is called from Java code through 236 | * Java_com_sample_helper_JUIHelper_JUICallbackHandler() 237 | */ 238 | virtual void DispatchEvent(const int32_t message, const int32_t param1, 239 | const int32_t param2) {} 240 | 241 | /* 242 | * Template for 1 parameter version of SetAttribute 243 | */ 244 | template 245 | bool SetAttribute(std::unordered_map &map, 246 | const char *strAttribute, const T t) { 247 | LOGI("Attribute '%s' updating", strAttribute); 248 | auto it = map.find(strAttribute); 249 | if (it != map.end()) { 250 | std::string s = std::string("set"); 251 | s += it->first; 252 | 253 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 254 | switch (it->second) { 255 | case ATTRIBUTE_PARAMETER_INT: 256 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 257 | obj_, s.c_str(), "(I)V", t); 258 | p.type = ATTRIBUTE_PARAMETER_INT; 259 | p.i = t; 260 | break; 261 | case ATTRIBUTE_PARAMETER_FLOAT: 262 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 263 | obj_, s.c_str(), "(F)V", t); 264 | p.type = ATTRIBUTE_PARAMETER_FLOAT; 265 | p.f = t; 266 | break; 267 | case ATTRIBUTE_PARAMETER_BOOLEAN: 268 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 269 | obj_, s.c_str(), "(Z)V", t); 270 | p.type = ATTRIBUTE_PARAMETER_BOOLEAN; 271 | p.b = t; 272 | break; 273 | default: 274 | LOGI("Attribute parameter does not match : %s", strAttribute); 275 | break; 276 | } 277 | } else { 278 | LOGI("Attribute '%s' not found", strAttribute); 279 | return false; 280 | } 281 | return true; 282 | } 283 | 284 | /* 285 | * Specialized Template for string version of SetAttribute 286 | */ 287 | bool SetAttribute(std::unordered_map &map, 288 | const char *strAttribute, const char *str) { 289 | auto it = map.find(strAttribute); 290 | if (it != map.end()) { 291 | std::string s = std::string("set"); 292 | s += it->first; 293 | 294 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 295 | switch (it->second) { 296 | case ATTRIBUTE_PARAMETER_STRING: { 297 | JNIEnv *env = 298 | ndk_helper::JNIHelper::GetInstance()->AttachCurrentThread(); 299 | jstring string = env->NewStringUTF(str); 300 | jstring stringGlobal = (jstring)env->NewGlobalRef(string); 301 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 302 | obj_, s.c_str(), "(Ljava/lang/CharSequence;)V", stringGlobal); 303 | env->DeleteGlobalRef(stringGlobal); 304 | env->DeleteLocalRef(string); 305 | 306 | p.type = ATTRIBUTE_PARAMETER_STRING; 307 | if (p.str != NULL) { 308 | if (p.str->compare(str) != 0) { 309 | delete p.str; 310 | p.str = new std::string(str); 311 | } 312 | } else { 313 | p.str = new std::string(str); 314 | } 315 | } break; 316 | default: 317 | LOGI("Attribute parameter does not match : %s", strAttribute); 318 | break; 319 | } 320 | } else { 321 | LOGI("Attribute '%s' not found", strAttribute); 322 | return false; 323 | } 324 | return true; 325 | } 326 | 327 | /* 328 | * Template for 2 parameters version of SetAttribute 329 | */ 330 | template 331 | bool SetAttribute(std::unordered_map &map, 332 | const char *strAttribute, T t, T2 t2) { 333 | auto it = map.find(strAttribute); 334 | if (it != map.end()) { 335 | std::string s = std::string("set"); 336 | s += it->first; 337 | 338 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 339 | switch (it->second) { 340 | case ATTRIBUTE_PARAMETER_IF: 341 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 342 | obj_, s.c_str(), "(IF)V", static_cast(t), 343 | static_cast(t2)); 344 | p.type = ATTRIBUTE_PARAMETER_IF; 345 | p.param_if.i1 = static_cast(t); 346 | p.param_if.f2 = static_cast(t2); 347 | break; 348 | case ATTRIBUTE_PARAMETER_FF: 349 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 350 | obj_, s.c_str(), "(FF)V", static_cast(t), 351 | static_cast(t2)); 352 | p.type = ATTRIBUTE_PARAMETER_FF; 353 | p.param_ff.f1 = static_cast(t); 354 | p.param_ff.f2 = static_cast(t2); 355 | break; 356 | default: 357 | LOGI("Attribute parameter does not match : %s", strAttribute); 358 | break; 359 | } 360 | } else { 361 | LOGI("Attribute '%s' not found", strAttribute); 362 | return false; 363 | } 364 | return true; 365 | } 366 | 367 | /* 368 | * Template for 4 parameters version of SetAttribute 369 | */ 370 | template 371 | bool SetAttribute(std::unordered_map &map, 372 | const char *strAttribute, T p1, T2 p2, T3 p3, T4 p4) { 373 | auto it = map.find(strAttribute); 374 | if (it != map.end()) { 375 | std::string s = std::string("set"); 376 | s += it->first; 377 | 378 | AttributeParameterStore &p = map_attribute_parameters[it->first]; 379 | switch (it->second) { 380 | case ATTRIBUTE_PARAMETER_IIII: 381 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 382 | obj_, s.c_str(), "(IIII)V", static_cast(p1), 383 | static_cast(p2), static_cast(p3), 384 | static_cast(p4)); 385 | p.type = ATTRIBUTE_PARAMETER_IIII; 386 | p.param_iiii.i1 = static_cast(p1); 387 | p.param_iiii.i2 = static_cast(p2); 388 | p.param_iiii.i3 = static_cast(p3); 389 | p.param_iiii.i4 = static_cast(p4); 390 | break; 391 | case ATTRIBUTE_PARAMETER_FFFI: 392 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 393 | obj_, s.c_str(), "(FFFI)V", static_cast(p1), 394 | static_cast(p2), static_cast(p3), 395 | static_cast(p4)); 396 | p.type = ATTRIBUTE_PARAMETER_FFFI; 397 | p.param_fffi.f1 = static_cast(p1); 398 | p.param_fffi.f2 = static_cast(p2); 399 | p.param_fffi.f3 = static_cast(p3); 400 | p.param_fffi.i = static_cast(p4); 401 | break; 402 | default: 403 | LOGI("Attribute parameter does not match : %s", strAttribute); 404 | break; 405 | } 406 | } else { 407 | LOGI("Attribute '%s' not found", strAttribute); 408 | return false; 409 | } 410 | return true; 411 | } 412 | 413 | /* 414 | * Retrieve attribute 415 | */ 416 | template 417 | bool GetAttribute(std::unordered_map &map, 418 | const char *strAttribute, T *p_value) { 419 | T ret; 420 | auto it = map.find(strAttribute); 421 | if (it != map.end()) { 422 | std::string s = std::string("get"); 423 | s += it->first; 424 | 425 | switch (it->second) { 426 | case ATTRIBUTE_PARAMETER_INT: 427 | ret = (T)ndk_helper::JNIHelper::GetInstance()->CallIntMethod( 428 | obj_, s.c_str(), "()I"); 429 | break; 430 | case ATTRIBUTE_PARAMETER_FLOAT: 431 | ret = (T)ndk_helper::JNIHelper::GetInstance()->CallFloatMethod( 432 | obj_, s.c_str(), "()F"); 433 | break; 434 | case ATTRIBUTE_PARAMETER_BOOLEAN: 435 | ret = (T)ndk_helper::JNIHelper::GetInstance()->CallBooleanMethod( 436 | obj_, s.c_str(), "()Z"); 437 | break; 438 | default: 439 | ret = 0; 440 | break; 441 | } 442 | } else { 443 | LOGI("Attribute '%s' not found", strAttribute); 444 | return false; 445 | } 446 | *p_value = ret; 447 | return true; 448 | } 449 | 450 | static IdFactory id_factory_; 451 | 452 | protected: 453 | std::unordered_map 454 | map_attribute_parameters; 455 | jobject obj_; 456 | jobject GetJobject() { return obj_; } 457 | }; 458 | 459 | /* 460 | * JUIView class 461 | */ 462 | class JUIView : public JUIBase { 463 | friend class JUIWindow; 464 | friend class JUIDialog; 465 | 466 | public: 467 | JUIView(); 468 | virtual ~JUIView(); 469 | 470 | /* 471 | * Add layout rule to the widget 472 | */ 473 | void AddRule(const int32_t layoutParameterIndex, const int32_t parameter); 474 | void AddRule(const int32_t layoutParameterIndex, const JUIView *parameter); 475 | /* 476 | * Set LayoutParams for RelativeLayout 477 | */ 478 | void SetLayoutParams(const int32_t width, const int32_t height); 479 | /* 480 | * Set LayoutParams for LinearLayout 481 | */ 482 | void SetLayoutParams(const int32_t width, const int32_t height, 483 | const float f); 484 | 485 | /* 486 | * Set Margins 487 | */ 488 | void SetMargins(const int32_t left, const int32_t top, const int32_t right, 489 | const int32_t bottom); 490 | 491 | /* 492 | * Set attribute of the widget 493 | * See attributes_ for available attribute names 494 | */ 495 | template 496 | bool SetAttribute(const char *strAttribute, const T t) { 497 | return SetAttribute(map_attributes_, strAttribute, t); 498 | } 499 | 500 | bool SetAttribute(const char *strAttribute, const char *str) { 501 | return JUIBase::SetAttribute(map_attributes_, strAttribute, str); 502 | } 503 | 504 | template 505 | bool SetAttribute(const char *strAttribute, T t, T2 t2) { 506 | return JUIBase::SetAttribute(map_attributes_, strAttribute, t, t2); 507 | } 508 | 509 | template 510 | bool SetAttribute(const char *strAttribute, T p1, T2 p2, T3 p3, T4 p4) { 511 | return JUIBase::SetAttribute(map_attributes_, strAttribute, p1, p2, p3, p4); 512 | } 513 | 514 | template 515 | bool GetAttributeA(const char *strAttribute, T *value) { 516 | return JUIBase::GetAttribute(map_attributes_, strAttribute, value); 517 | } 518 | 519 | private: 520 | const static AttributeType attributes_[]; 521 | int32_t array_current_rules_[LAYOUT_PARAMETER_COUNT]; 522 | 523 | int32_t layoutWidth_; 524 | int32_t layoutHeight_; 525 | float layoutWeight_; 526 | 527 | int32_t marginLeft_; 528 | int32_t marginRight_; 529 | int32_t marginTop_; 530 | int32_t marginBottom_; 531 | 532 | protected: 533 | static std::unordered_map map_attributes_; 534 | 535 | void RestoreParameters(std::unordered_map &map); 536 | virtual void Restore() = 0; 537 | }; 538 | 539 | } // namespace jui_helper 540 | 541 | #endif // _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_JUI_HELPER_JAVAUI_VIEW_H_ 542 | 543 | -------------------------------------------------------------------------------- /app/src/main/cpp/jui_helper/JavaUI_Window.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "JavaUI.h" 18 | 19 | namespace jui_helper { 20 | 21 | /* 22 | * JUIWindow 23 | */ 24 | JUIWindow::JUIWindow() 25 | : activity_(NULL), 26 | suspended_(false), 27 | windowDestroyed_(false), 28 | jni_helper_java_ref_(NULL), 29 | jni_helper_java_class_(NULL), 30 | dialog_(NULL) { 31 | JUIBase::id_factory_.insert(reinterpret_cast(this)); 32 | views_.clear(); 33 | } 34 | 35 | JUIWindow::~JUIWindow() { 36 | ndk_helper::JNIHelper *helper = ndk_helper::JNIHelper::GetInstance(); 37 | JNIEnv *env = helper->AttachCurrentThread(); 38 | 39 | env->DeleteGlobalRef(jni_helper_java_ref_); 40 | env->DeleteGlobalRef(jni_helper_java_class_); 41 | 42 | JUIBase::id_factory_.remove(reinterpret_cast(this)); 43 | 44 | } 45 | 46 | /* 47 | * Singleton implementation, JUIWindow is a single instance that put 48 | * all of widget on that 49 | */ 50 | JUIWindow *JUIWindow::GetInstance() { 51 | static JUIWindow window; 52 | return &window; 53 | } 54 | 55 | jobject JUIWindow::GetHelperClassInstance() { 56 | if (JUIWindow::GetInstance()->jni_helper_java_ref_ == NULL) 57 | LOGE("JUIWindow::GetHelperClass(): Null helper class instance!"); 58 | return JUIWindow::GetInstance()->jni_helper_java_ref_; 59 | } 60 | 61 | jclass JUIWindow::GetHelperClass() { 62 | if (JUIWindow::GetInstance()->jni_helper_java_class_ == NULL) 63 | LOGE("JUIWindow::GetHelperClass(): Null helper class!"); 64 | return JUIWindow::GetInstance()->jni_helper_java_class_; 65 | } 66 | 67 | /* 68 | * Init 69 | */ 70 | void JUIWindow::Init(ANativeActivity *activity, const char *helper_class_name) { 71 | JUIWindow &window = *GetInstance(); 72 | LOGI("Initialized Java UI"); 73 | if (window.activity_) { 74 | LOGI("The class has been already initialized with an activity"); 75 | return; 76 | } 77 | window.activity_ = activity; 78 | 79 | ndk_helper::JNIHelper &helper = *ndk_helper::JNIHelper::GetInstance(); 80 | JNIEnv *env = helper.AttachCurrentThread(); 81 | 82 | if (helper_class_name != NULL && window.jni_helper_java_class_ == NULL && 83 | window.jni_helper_java_ref_ == NULL) { 84 | // Instantiate JUIHelper class 85 | jclass cls = helper.RetrieveClass(env, helper_class_name); 86 | 87 | window.jni_helper_java_class_ = (jclass)env->NewGlobalRef(cls); 88 | 89 | jmethodID constructor = 90 | env->GetMethodID(window.jni_helper_java_class_, "", 91 | "(Landroid/app/NativeActivity;)V"); 92 | window.jni_helper_java_ref_ = env->NewObject(window.jni_helper_java_class_, 93 | constructor, activity->clazz); 94 | window.jni_helper_java_ref_ = 95 | env->NewGlobalRef(window.jni_helper_java_ref_); 96 | env->DeleteLocalRef(cls); 97 | } 98 | 99 | // Create popupWindow 100 | jmethodID mid = env->GetMethodID( 101 | window.jni_helper_java_class_, "createPopupWindow", 102 | "(Landroid/app/NativeActivity;)Landroid/widget/PopupWindow;"); 103 | jobject obj = env->CallObjectMethod(window.jni_helper_java_ref_, mid, 104 | window.activity_->clazz); 105 | jobject objGlobal = env->NewGlobalRef(obj); 106 | 107 | if (objGlobal == NULL) { 108 | LOGI("Failed creating popupWindow"); 109 | } 110 | window.popupWindow_ = objGlobal; 111 | env->DeleteLocalRef(obj); 112 | } 113 | 114 | /* 115 | * Suspend 116 | */ 117 | void JUIWindow::Suspend(const int32_t cmd) { 118 | // Lock mutex 119 | std::lock_guard lock(mutex_); 120 | 121 | if (cmd == APP_CMD_TERM_WINDOW) windowDestroyed_ = true; 122 | 123 | if (suspended_ == true) { 124 | LOGI("The window has been suspended already"); 125 | return; 126 | } 127 | 128 | LOGI("Suspending JUI"); 129 | if (popupWindow_) { 130 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 131 | jni_helper_java_ref_, "suspendPopupWindow", 132 | "(Landroid/widget/PopupWindow;)V", popupWindow_); 133 | popupWindow_ = NULL; 134 | } 135 | 136 | if (dialog_) { 137 | dialog_->Suspend(); 138 | } 139 | 140 | suspended_ = true; 141 | } 142 | 143 | /* 144 | * Resume 145 | */ 146 | void JUIWindow::Resume(ANativeActivity *activity, const int32_t cmd) { 147 | if (suspended_ != true) { 148 | LOGI("The window has not been suspended"); 149 | return; 150 | } 151 | 152 | /* 153 | * Special case handling for sleep 154 | * In case of the situation that 155 | * 1) screen has been slept 156 | * 2) but lockscreen has not been initiated 157 | * Only a message coming is: 158 | * APP_CMD_START & APP_CMD_TERM_RESUME 159 | * Usually windows should not be initialized at these message, 160 | * but we do it only for the situation 161 | */ 162 | LOGI("resuming %d, %d", cmd, windowDestroyed_); 163 | if (windowDestroyed_ == true && cmd == APP_CMD_RESUME) { 164 | LOGI("Won't resume JUI"); 165 | return; 166 | } 167 | 168 | LOGI("Resuming JUI"); 169 | 170 | // Re-initialize popupWindow 171 | activity_ = NULL; 172 | Init(activity); 173 | 174 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 175 | jni_helper_java_ref_, "resumePopupWindow", 176 | "(Landroid/app/NativeActivity;Landroid/widget/PopupWindow;)V", 177 | activity_->clazz, popupWindow_); 178 | 179 | // Restore widgets 180 | auto itBegin = views_.begin(); 181 | auto itEnd = views_.end(); 182 | while (itBegin != itEnd) { 183 | // Restore 184 | (*itBegin)->Restore(); 185 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 186 | jni_helper_java_ref_, "addView", "(Landroid/view/View;)V", 187 | (*itBegin)->GetJobject()); 188 | 189 | itBegin++; 190 | } 191 | 192 | // Restore dialog 193 | if (dialog_) dialog_->Resume(activity); 194 | 195 | LOGI("Resumed JUI"); 196 | suspended_ = false; 197 | windowDestroyed_ = false; 198 | } 199 | 200 | /* 201 | * Close 202 | */ 203 | void JUIWindow::Close() { 204 | LOGI("Closing JUI"); 205 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 206 | jni_helper_java_ref_, "closePopupWindow", 207 | "(Landroid/widget/PopupWindow;)V", popupWindow_); 208 | ndk_helper::JNIHelper::GetInstance()->DeleteObject(popupWindow_); 209 | 210 | auto itBegin = views_.begin(); 211 | auto itEnd = views_.end(); 212 | while (itBegin != itEnd) { 213 | delete *itBegin; 214 | itBegin++; 215 | } 216 | 217 | views_.clear(); 218 | 219 | if (dialog_) dialog_->Close(); 220 | 221 | activity_ = NULL; 222 | popupWindow_ = NULL; 223 | } 224 | 225 | /* 226 | * Add JUIView to popup window 227 | */ 228 | void JUIWindow::AddView(JUIView *view) { 229 | ndk_helper::JNIHelper::GetInstance()->CallVoidMethod( 230 | jni_helper_java_ref_, "addView", "(Landroid/view/View;)V", 231 | view->GetJobject()); 232 | views_.push_back(view); 233 | } 234 | 235 | jobject JUIWindow::CreateWidget(const char *strWidgetName, void *id) { 236 | if (activity_ == NULL) { 237 | LOGI( 238 | "JNIHelper has not been initialized. Call init() to initialize the " 239 | "helper"); 240 | return NULL; 241 | } 242 | 243 | ndk_helper::JNIHelper *helper = ndk_helper::JNIHelper::GetInstance(); 244 | JNIEnv *env = helper->AttachCurrentThread(); 245 | 246 | // Create widget 247 | jstring name = env->NewStringUTF(strWidgetName); 248 | static jmethodID mid = NULL; 249 | if (mid == NULL) { 250 | mid = env->GetMethodID(jni_helper_java_class_, "createWidget", 251 | "(Ljava/lang/String;I)Landroid/view/View;"); 252 | if (mid == NULL) { 253 | LOGI( 254 | "method ID 'createWidget', " 255 | "'(Ljava/lang/String;I)Landroid/view/View;' not found"); 256 | return NULL; 257 | } 258 | } 259 | 260 | jobject obj = env->CallObjectMethod(jni_helper_java_ref_, mid, name, JUIBase::id_factory_.getId(reinterpret_cast(id))); 261 | jobject objGlobal = env->NewGlobalRef(obj); 262 | env->DeleteLocalRef(name); 263 | env->DeleteLocalRef(obj); 264 | 265 | return objGlobal; 266 | } 267 | 268 | jobject JUIWindow::CreateWidget(const char *strWidgetName, void *id, 269 | const int32_t param) { 270 | if (activity_ == NULL) { 271 | LOGI( 272 | "JNIHelper has not been initialized. Call init() to initialize the " 273 | "helper"); 274 | return NULL; 275 | } 276 | 277 | ndk_helper::JNIHelper *helper = ndk_helper::JNIHelper::GetInstance(); 278 | JNIEnv *env = helper->AttachCurrentThread(); 279 | 280 | // Create widget 281 | jstring name = env->NewStringUTF(strWidgetName); 282 | static jmethodID mid = NULL; 283 | if (mid == NULL) { 284 | mid = env->GetMethodID(jni_helper_java_class_, "createWidget", 285 | "(Ljava/lang/String;II)Landroid/view/View;"); 286 | if (mid == NULL) { 287 | LOGI( 288 | "method ID 'createWidget', " 289 | "'(Ljava/lang/String;II)Landroid/view/View;' not found"); 290 | return NULL; 291 | } 292 | } 293 | 294 | jobject obj = env->CallObjectMethod(jni_helper_java_ref_, mid, name, 295 | JUIBase::id_factory_.getId(reinterpret_cast(id)), (int32_t) param); 296 | 297 | jobject objGlobal = env->NewGlobalRef(obj); 298 | env->DeleteLocalRef(name); 299 | env->DeleteLocalRef(objGlobal); 300 | return objGlobal; 301 | } 302 | 303 | void JUIWindow::CloseWidget(jobject obj) { 304 | if (activity_ == NULL) { 305 | LOGI( 306 | "JNIHelper has not been initialized. Call init() to initialize the " 307 | "helper"); 308 | return; 309 | } 310 | 311 | ndk_helper::JNIHelper *helper = ndk_helper::JNIHelper::GetInstance(); 312 | JNIEnv *env = helper->AttachCurrentThread(); 313 | 314 | static jmethodID mid = NULL; 315 | if (mid == NULL) { 316 | mid = env->GetMethodID(jni_helper_java_class_, "closeWidget", 317 | "(Landroid/view/View;)V"); 318 | if (mid == NULL) { 319 | LOGI("method not found"); 320 | return; 321 | } 322 | } 323 | 324 | env->CallVoidMethod(jni_helper_java_ref_, mid, obj); 325 | env->DeleteGlobalRef(obj); 326 | 327 | return; 328 | } 329 | 330 | } // namespace jui_helper 331 | -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | ## 15 | 16 | # For more information about using CMake with Android Studio, read the 17 | # documentation: https://d.android.com/studio/projects/add-native-code.html 18 | 19 | # Sets the minimum version of CMake required to build the native library. 20 | 21 | cmake_minimum_required(VERSION 3.4.1) 22 | set(CMAKE_VERBOSE_MAKEFILE on) 23 | 24 | project(ndkhelper) 25 | 26 | add_library(ndkhelper STATIC 27 | gl3stub.cpp 28 | GLContext.cpp 29 | JNIHelper.cpp 30 | ) 31 | 32 | target_include_directories(ndkhelper PRIVATE 33 | ${ANDROID_NDK}/sources/android/native_app_glue 34 | ) 35 | set_target_properties(ndkhelper 36 | PROPERTIES 37 | ARCHIVE_OUTPUT_DIRECTORY 38 | "${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}") 39 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/GLContext.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //-------------------------------------------------------------------------------- 18 | // GLContext.cpp 19 | //-------------------------------------------------------------------------------- 20 | //-------------------------------------------------------------------------------- 21 | // includes 22 | //-------------------------------------------------------------------------------- 23 | #include 24 | #include "GLContext.h" 25 | #include "gl3stub.h" 26 | 27 | namespace ndk_helper { 28 | 29 | //-------------------------------------------------------------------------------- 30 | // eGLContext 31 | //-------------------------------------------------------------------------------- 32 | const int32_t SWAPINTERVAL_DEFAULT = 1; 33 | 34 | //-------------------------------------------------------------------------------- 35 | // Ctor 36 | //-------------------------------------------------------------------------------- 37 | GLContext::GLContext() 38 | : window_(nullptr), 39 | display_(EGL_NO_DISPLAY), 40 | surface_(EGL_NO_SURFACE), 41 | context_(EGL_NO_CONTEXT), 42 | screen_width_(0), 43 | screen_height_(0), 44 | msaa_size_(1), 45 | restoreInterval_(false), 46 | swapInterval_(SWAPINTERVAL_DEFAULT), 47 | gles_initialized_(false), 48 | egl_context_initialized_(false), 49 | es3_supported_(false), 50 | gl_version_(0), 51 | context_valid_(false) {} 52 | 53 | void GLContext::InitGLES() { 54 | if (gles_initialized_) return; 55 | // 56 | // Initialize OpenGL ES 3 if available 57 | // 58 | const char *versionStr = (const char *)glGetString(GL_VERSION); 59 | if (strstr(versionStr, "OpenGL ES 3.") && gl3stubInit()) { 60 | es3_supported_ = true; 61 | gl_version_ = 3.0f; 62 | } else { 63 | gl_version_ = 2.0f; 64 | } 65 | 66 | gles_initialized_ = true; 67 | } 68 | 69 | //-------------------------------------------------------------------------------- 70 | // Dtor 71 | //-------------------------------------------------------------------------------- 72 | GLContext::~GLContext() { Terminate(); } 73 | 74 | bool GLContext::Init(ANativeWindow *window, const int32_t msaa) { 75 | if (egl_context_initialized_) return true; 76 | 77 | // 78 | // Initialize EGL 79 | // 80 | window_ = window; 81 | msaa_size_ = msaa; 82 | InitEGLSurface(); 83 | InitEGLContext(); 84 | InitGLES(); 85 | 86 | egl_context_initialized_ = true; 87 | 88 | return true; 89 | } 90 | 91 | bool GLContext::InitEGLSurface() { 92 | display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); 93 | eglInitialize(display_, 0, 0); 94 | 95 | /* 96 | * Here specify the attributes of the desired configuration. 97 | * Below, we select an EGLConfig with at least 8 bits per color 98 | * component compatible with on-screen windows 99 | */ 100 | const EGLint attribs[] = {EGL_RENDERABLE_TYPE, 101 | EGL_OPENGL_ES2_BIT, // Request opengl ES2.0 102 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, 103 | EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 104 | 24, EGL_SAMPLES, msaa_size_, EGL_NONE}; 105 | color_size_ = 8; 106 | depth_size_ = 24; 107 | 108 | EGLint num_configs; 109 | eglChooseConfig(display_, attribs, &config_, 1, &num_configs); 110 | 111 | if (msaa_size_ > 1 && !num_configs) { 112 | LOGW("No EGL config with MSAA"); 113 | // Fall back to non MSAA 114 | const EGLint attribs[] = {EGL_RENDERABLE_TYPE, 115 | EGL_OPENGL_ES2_BIT, // Request opengl ES2.0 116 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 117 | 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, 118 | EGL_DEPTH_SIZE, 24, EGL_NONE}; 119 | msaa_size_ = 1; 120 | eglChooseConfig(display_, attribs, &config_, 1, &num_configs); 121 | } 122 | 123 | if (!num_configs) { 124 | LOGW("No 24bit depth buffer"); 125 | 126 | // Fall back to 16bit depth buffer 127 | const EGLint attribs[] = {EGL_RENDERABLE_TYPE, 128 | EGL_OPENGL_ES2_BIT, // Request opengl ES2.0 129 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 130 | 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, 131 | EGL_DEPTH_SIZE, 16, EGL_NONE}; 132 | eglChooseConfig(display_, attribs, &config_, 1, &num_configs); 133 | depth_size_ = 16; 134 | } 135 | 136 | if (!num_configs) { 137 | LOGW("Unable to retrieve EGL config"); 138 | return false; 139 | } 140 | 141 | surface_ = eglCreateWindowSurface(display_, config_, window_, NULL); 142 | eglQuerySurface(display_, surface_, EGL_WIDTH, &screen_width_); 143 | eglQuerySurface(display_, surface_, EGL_HEIGHT, &screen_height_); 144 | 145 | return true; 146 | } 147 | 148 | bool GLContext::InitEGLContext() { 149 | const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 150 | 2, // Request opengl ES2.0 151 | EGL_NONE}; 152 | context_ = eglCreateContext(display_, config_, NULL, context_attribs); 153 | 154 | if (eglMakeCurrent(display_, surface_, surface_, context_) == EGL_FALSE) { 155 | LOGW("Unable to eglMakeCurrent"); 156 | return false; 157 | } 158 | 159 | context_valid_ = true; 160 | return true; 161 | } 162 | 163 | EGLint GLContext::Swap() { 164 | bool b = eglSwapBuffers(display_, surface_); 165 | if (!b) { 166 | EGLint err = eglGetError(); 167 | if (err == EGL_BAD_SURFACE) { 168 | // Recreate surface 169 | InitEGLSurface(); 170 | err = EGL_SUCCESS; // Still consider glContext is valid 171 | } else if (err == EGL_CONTEXT_LOST || err == EGL_BAD_CONTEXT) { 172 | // Context has been lost!! 173 | context_valid_ = false; 174 | Terminate(); 175 | InitEGLContext(); 176 | } 177 | 178 | return err; 179 | } 180 | if (restoreInterval_ && swapInterval_ != SWAPINTERVAL_DEFAULT) { 181 | eglSwapInterval(display_, swapInterval_); // Restore Swap interval 182 | } 183 | return EGL_SUCCESS; 184 | } 185 | 186 | void GLContext::Terminate() { 187 | if (display_ != EGL_NO_DISPLAY) { 188 | eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 189 | if (context_ != EGL_NO_CONTEXT) { 190 | eglDestroyContext(display_, context_); 191 | } 192 | 193 | if (surface_ != EGL_NO_SURFACE) { 194 | eglDestroySurface(display_, surface_); 195 | } 196 | eglTerminate(display_); 197 | } 198 | 199 | display_ = EGL_NO_DISPLAY; 200 | context_ = EGL_NO_CONTEXT; 201 | surface_ = EGL_NO_SURFACE; 202 | context_valid_ = false; 203 | } 204 | 205 | EGLint GLContext::Resume(ANativeWindow *window) { 206 | if (egl_context_initialized_ == false) { 207 | Init(window, msaa_size_); 208 | return EGL_SUCCESS; 209 | } 210 | 211 | int32_t original_widhth = screen_width_; 212 | int32_t original_height = screen_height_; 213 | 214 | // Create surface 215 | window_ = window; 216 | surface_ = eglCreateWindowSurface(display_, config_, window_, NULL); 217 | eglQuerySurface(display_, surface_, EGL_WIDTH, &screen_width_); 218 | eglQuerySurface(display_, surface_, EGL_HEIGHT, &screen_height_); 219 | 220 | if (screen_width_ != original_widhth || screen_height_ != original_height) { 221 | // Screen resized 222 | LOGI("Screen resized"); 223 | } 224 | 225 | if (eglMakeCurrent(display_, surface_, surface_, context_) == EGL_TRUE) 226 | return EGL_SUCCESS; 227 | 228 | EGLint err = eglGetError(); 229 | LOGW("Unable to eglMakeCurrent %d", err); 230 | 231 | if (err == EGL_CONTEXT_LOST) { 232 | // Recreate context 233 | LOGI("Re-creating egl context"); 234 | InitEGLContext(); 235 | } else { 236 | // Recreate surface 237 | Terminate(); 238 | InitEGLSurface(); 239 | InitEGLContext(); 240 | } 241 | 242 | return err; 243 | } 244 | 245 | void GLContext::Suspend() { 246 | if (surface_ != EGL_NO_SURFACE) { 247 | eglDestroySurface(display_, surface_); 248 | surface_ = EGL_NO_SURFACE; 249 | } 250 | restoreInterval_ = true; 251 | } 252 | 253 | bool GLContext::Invalidate() { 254 | Terminate(); 255 | 256 | egl_context_initialized_ = false; 257 | return true; 258 | } 259 | 260 | bool GLContext::CheckExtension(const char *extension) { 261 | if (extension == NULL) return false; 262 | 263 | std::string extensions = std::string(reinterpret_cast(glGetString(GL_EXTENSIONS))); 264 | std::string str = std::string(extension); 265 | str.append(" "); 266 | 267 | size_t pos = 0; 268 | if (extensions.find(extension, pos) != std::string::npos) { 269 | return true; 270 | } 271 | 272 | return false; 273 | } 274 | 275 | } // namespace ndk_helper 276 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/GLContext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //----------------------------------------------------------------------------- 18 | // GLContext.h 19 | //----------------------------------------------------------------------------- 20 | #ifndef _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_GLCONTEXT_H_ 21 | #define _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_GLCONTEXT_H_ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "JNIHelper.h" 28 | 29 | namespace ndk_helper { 30 | 31 | //----------------------------------------------------------------------------- 32 | // Constants 33 | //----------------------------------------------------------------------------- 34 | 35 | //----------------------------------------------------------------------------- 36 | // Class 37 | //----------------------------------------------------------------------------- 38 | 39 | /****************************************************************** 40 | * OpenGL context handler 41 | * The class handles OpenGL and EGL context based on Android activity life cycle 42 | * The caller needs to call corresponding methods for each activity life cycle 43 | * events as it's done in sample codes. 44 | * 45 | * Also the class initializes OpenGL ES3 when the compatible driver is installed 46 | * in the device. 47 | * getGLVersion() returns 3.0~ when the device supports OpenGLES3.0 48 | * 49 | * Thread safety: OpenGL context is expecting used within dedicated single 50 | * thread, 51 | * thus GLContext class is not designed as a thread-safe 52 | */ 53 | class GLContext { 54 | private: 55 | // EGL configurations 56 | ANativeWindow *window_; 57 | EGLDisplay display_; 58 | EGLSurface surface_; 59 | EGLContext context_; 60 | EGLConfig config_; 61 | 62 | // Screen parameters 63 | int32_t screen_width_; 64 | int32_t screen_height_; 65 | int32_t color_size_; 66 | int32_t depth_size_; 67 | int32_t msaa_size_; 68 | 69 | // EGL Swap interval 70 | bool restoreInterval_; 71 | int32_t swapInterval_; 72 | 73 | // Flags 74 | bool gles_initialized_; 75 | bool egl_context_initialized_; 76 | bool es3_supported_; 77 | float gl_version_; 78 | bool context_valid_; 79 | 80 | void InitGLES(); 81 | void Terminate(); 82 | bool InitEGLSurface(); 83 | bool InitEGLContext(); 84 | 85 | GLContext(GLContext const &); 86 | void operator=(GLContext const &); 87 | GLContext(); 88 | virtual ~GLContext(); 89 | 90 | public: 91 | static GLContext *GetInstance() { 92 | // Singleton 93 | static GLContext instance; 94 | 95 | return &instance; 96 | } 97 | 98 | bool Init(ANativeWindow *window, const int32_t msaa = 1); 99 | EGLint Swap(); 100 | bool Invalidate(); 101 | 102 | void Suspend(); 103 | EGLint Resume(ANativeWindow *window); 104 | 105 | int32_t GetScreenWidth() { return screen_width_; } 106 | int32_t GetScreenHeight() { return screen_height_; } 107 | 108 | int32_t GetBufferColorSize() { return color_size_; } 109 | int32_t GetBufferDepthSize() { return depth_size_; } 110 | int32_t GetMSAASize() { return msaa_size_; } 111 | 112 | float GetGLVersion() { return gl_version_; } 113 | bool CheckExtension(const char *extension); 114 | 115 | /* 116 | * Set SwapInterval to EGL context 117 | * SwapInterval:1 indicates that frame rate is synchronous to vblank interval 118 | * which is usually 59.94hz. In API 17 (JellyBeans)~, the API accepts value 119 | * of 0, which does not sync to vblank. 120 | */ 121 | void SetSwapInterval(const int32_t interval) { 122 | eglSwapInterval(display_, interval); 123 | swapInterval_ = interval; 124 | } 125 | }; 126 | 127 | } // namespace ndk_helper 128 | 129 | #endif // _USR_LOCAL_GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_GLCONTEXT_H_ 130 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/JNIHelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "JNIHelper.h" 22 | 23 | namespace ndk_helper { 24 | 25 | #define NATIVEACTIVITY_CLASS_NAME "android/app/NativeActivity" 26 | 27 | /* 28 | * JNI Helper functions 29 | */ 30 | 31 | /* 32 | * Singleton 33 | */ 34 | JNIHelper *JNIHelper::GetInstance() { 35 | static JNIHelper helper; 36 | return &helper; 37 | } 38 | 39 | /* 40 | * Ctor 41 | */ 42 | JNIHelper::JNIHelper() : activity_(NULL) {} 43 | 44 | /* 45 | * Dtor 46 | */ 47 | JNIHelper::~JNIHelper() { 48 | // Lock mutex 49 | std::lock_guard lock(mutex_); 50 | 51 | JNIEnv *env = AttachCurrentThread(); 52 | env->DeleteGlobalRef(jni_helper_java_ref_); 53 | env->DeleteGlobalRef(jni_helper_java_class_); 54 | } 55 | 56 | /* 57 | * Init 58 | */ 59 | void JNIHelper::Init(ANativeActivity *activity, const char *helper_class_name) { 60 | JNIHelper &helper = *GetInstance(); 61 | 62 | helper.activity_ = activity; 63 | 64 | // Lock mutex 65 | std::lock_guard lock(helper.mutex_); 66 | 67 | JNIEnv *env = helper.AttachCurrentThread(); 68 | 69 | // Retrieve app bundle id 70 | jclass android_content_Context = env->GetObjectClass(helper.activity_->clazz); 71 | jmethodID midGetPackageName = env->GetMethodID( 72 | android_content_Context, "getPackageName", "()Ljava/lang/String;"); 73 | 74 | jstring packageName = (jstring)env->CallObjectMethod(helper.activity_->clazz, 75 | midGetPackageName); 76 | const char *appname = env->GetStringUTFChars(packageName, NULL); 77 | helper.app_bunlde_name_ = std::string(appname); 78 | 79 | // Instantiate JNIHelper class 80 | jclass cls = helper.RetrieveClass(env, helper_class_name); 81 | helper.jni_helper_java_class_ = (jclass)env->NewGlobalRef(cls); 82 | 83 | jmethodID constructor = 84 | env->GetMethodID(helper.jni_helper_java_class_, "", 85 | "(Landroid/app/NativeActivity;)V"); 86 | 87 | helper.jni_helper_java_ref_ = env->NewObject(helper.jni_helper_java_class_, 88 | constructor, activity->clazz); 89 | helper.jni_helper_java_ref_ = env->NewGlobalRef(helper.jni_helper_java_ref_); 90 | 91 | // Get app label 92 | jstring labelName = (jstring)helper.CallObjectMethod("getApplicationName", 93 | "()Ljava/lang/String;"); 94 | const char *label = env->GetStringUTFChars(labelName, NULL); 95 | helper.app_label_ = std::string(label); 96 | 97 | env->ReleaseStringUTFChars(packageName, appname); 98 | env->ReleaseStringUTFChars(labelName, label); 99 | env->DeleteLocalRef(packageName); 100 | env->DeleteLocalRef(labelName); 101 | env->DeleteLocalRef(cls); 102 | } 103 | 104 | void JNIHelper::Init(ANativeActivity *activity, const char *helper_class_name, 105 | const char *native_soname) { 106 | Init(activity, helper_class_name); 107 | if (native_soname) { 108 | JNIHelper &helper = *GetInstance(); 109 | // Lock mutex 110 | std::lock_guard lock(helper.mutex_); 111 | 112 | JNIEnv *env = helper.AttachCurrentThread(); 113 | 114 | // Setup soname 115 | jstring soname = env->NewStringUTF(native_soname); 116 | 117 | jmethodID mid = env->GetMethodID(helper.jni_helper_java_class_, 118 | "loadLibrary", "(Ljava/lang/String;)V"); 119 | env->CallVoidMethod(helper.jni_helper_java_ref_, mid, soname); 120 | 121 | env->DeleteLocalRef(soname); 122 | } 123 | } 124 | 125 | std::string JNIHelper::GetNearbyConnectionServiceID() { 126 | if (activity_ == NULL) { 127 | LOGI( 128 | "JNIHelper has not been initialized. Call init() to initialize the " 129 | "helper"); 130 | return std::string(""); 131 | } 132 | 133 | JNIEnv *env = AttachCurrentThread(); 134 | 135 | std::string service_id(""); 136 | jmethodID mid = 137 | env->GetMethodID(jni_helper_java_class_, "getNearbyConnectionServiceID", 138 | "()Ljava/lang/String;"); 139 | if (NULL == mid) { 140 | LOGE("JNIHelper could not find function name getNearbyConnectionServiceID"); 141 | return service_id; 142 | } 143 | 144 | jstring resultJNIStr = 145 | (jstring)env->CallObjectMethod(jni_helper_java_ref_, mid); 146 | const char *resultCStr = env->GetStringUTFChars(resultJNIStr, NULL); 147 | if (NULL == resultCStr) { 148 | LOGE("Java GetNearbyConnectionServiceID() returned NULL string"); 149 | return service_id; 150 | } 151 | service_id = std::string(resultCStr); 152 | env->ReleaseStringUTFChars(resultJNIStr, resultCStr); 153 | env->DeleteLocalRef(resultJNIStr); 154 | return service_id; 155 | } 156 | /* 157 | * Misc implementations 158 | */ 159 | jclass JNIHelper::RetrieveClass(JNIEnv *jni, const char *class_name) { 160 | jclass activity_class = jni->FindClass(NATIVEACTIVITY_CLASS_NAME); 161 | jmethodID get_class_loader = jni->GetMethodID( 162 | activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); 163 | jobject cls = jni->CallObjectMethod(activity_->clazz, get_class_loader); 164 | jclass class_loader = jni->FindClass("java/lang/ClassLoader"); 165 | jmethodID find_class = jni->GetMethodID( 166 | class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); 167 | 168 | jstring str_class_name = jni->NewStringUTF(class_name); 169 | jclass class_retrieved = 170 | (jclass)jni->CallObjectMethod(cls, find_class, str_class_name); 171 | jni->DeleteLocalRef(str_class_name); 172 | jni->DeleteLocalRef(activity_class); 173 | jni->DeleteLocalRef(class_loader); 174 | return class_retrieved; 175 | } 176 | 177 | void JNIHelper::DeleteObject(jobject obj) { 178 | if (obj == NULL) { 179 | LOGI("obj can not be NULL"); 180 | return; 181 | } 182 | 183 | JNIEnv *env = AttachCurrentThread(); 184 | env->DeleteGlobalRef(obj); 185 | } 186 | 187 | jobject JNIHelper::CallObjectMethod(const char *strMethodName, 188 | const char *strSignature, ...) { 189 | if (activity_ == NULL) { 190 | LOGI( 191 | "JNIHelper has not been initialized. Call init() to initialize the " 192 | "helper"); 193 | return NULL; 194 | } 195 | 196 | JNIEnv *env = AttachCurrentThread(); 197 | jmethodID mid = 198 | env->GetMethodID(jni_helper_java_class_, strMethodName, strSignature); 199 | if (mid == NULL) { 200 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 201 | return NULL; 202 | } 203 | 204 | va_list args; 205 | va_start(args, strSignature); 206 | jobject obj = env->CallObjectMethodV(jni_helper_java_ref_, mid, args); 207 | va_end(args); 208 | 209 | return obj; 210 | } 211 | 212 | void JNIHelper::CallVoidMethod(const char *strMethodName, 213 | const char *strSignature, ...) { 214 | if (activity_ == NULL) { 215 | LOGI( 216 | "JNIHelper has not been initialized. Call init() to initialize the " 217 | "helper"); 218 | return; 219 | } 220 | 221 | JNIEnv *env = AttachCurrentThread(); 222 | jmethodID mid = 223 | env->GetMethodID(jni_helper_java_class_, strMethodName, strSignature); 224 | if (mid == NULL) { 225 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 226 | return; 227 | } 228 | va_list args; 229 | va_start(args, strSignature); 230 | env->CallVoidMethodV(jni_helper_java_ref_, mid, args); 231 | va_end(args); 232 | 233 | return; 234 | } 235 | 236 | jobject JNIHelper::CallObjectMethod(jobject object, const char *strMethodName, 237 | const char *strSignature, ...) { 238 | if (activity_ == NULL) { 239 | LOGI( 240 | "JNIHelper has not been initialized. Call init() to initialize the " 241 | "helper"); 242 | return NULL; 243 | } 244 | 245 | JNIEnv *env = AttachCurrentThread(); 246 | jclass cls = env->GetObjectClass(object); 247 | jmethodID mid = env->GetMethodID(cls, strMethodName, strSignature); 248 | if (mid == NULL) { 249 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 250 | return NULL; 251 | } 252 | 253 | va_list args; 254 | va_start(args, strSignature); 255 | jobject obj = env->CallObjectMethodV(object, mid, args); 256 | va_end(args); 257 | 258 | env->DeleteLocalRef(cls); 259 | return obj; 260 | } 261 | 262 | void JNIHelper::CallVoidMethod(jobject object, const char *strMethodName, 263 | const char *strSignature, ...) { 264 | if (activity_ == NULL) { 265 | LOGI( 266 | "JNIHelper has not been initialized. Call init() to initialize the " 267 | "helper"); 268 | return; 269 | } 270 | 271 | JNIEnv *env = AttachCurrentThread(); 272 | jclass cls = env->GetObjectClass(object); 273 | jmethodID mid = env->GetMethodID(cls, strMethodName, strSignature); 274 | if (mid == NULL) { 275 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 276 | return; 277 | } 278 | 279 | va_list args; 280 | va_start(args, strSignature); 281 | env->CallVoidMethodV(object, mid, args); 282 | va_end(args); 283 | 284 | env->DeleteLocalRef(cls); 285 | return; 286 | } 287 | 288 | float JNIHelper::CallFloatMethod(jobject object, const char *strMethodName, 289 | const char *strSignature, ...) { 290 | float f = 0.f; 291 | if (activity_ == NULL) { 292 | LOGI( 293 | "JNIHelper has not been initialized. Call init() to initialize the " 294 | "helper"); 295 | return f; 296 | } 297 | 298 | JNIEnv *env = AttachCurrentThread(); 299 | jclass cls = env->GetObjectClass(object); 300 | jmethodID mid = env->GetMethodID(cls, strMethodName, strSignature); 301 | if (mid == NULL) { 302 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 303 | return f; 304 | } 305 | va_list args; 306 | va_start(args, strSignature); 307 | f = env->CallFloatMethodV(object, mid, args); 308 | va_end(args); 309 | 310 | env->DeleteLocalRef(cls); 311 | return f; 312 | } 313 | 314 | int32_t JNIHelper::CallIntMethod(jobject object, const char *strMethodName, 315 | const char *strSignature, ...) { 316 | int32_t i = 0; 317 | if (activity_ == NULL) { 318 | LOGI( 319 | "JNIHelper has not been initialized. Call init() to initialize the " 320 | "helper"); 321 | return i; 322 | } 323 | 324 | JNIEnv *env = AttachCurrentThread(); 325 | jclass cls = env->GetObjectClass(object); 326 | jmethodID mid = env->GetMethodID(cls, strMethodName, strSignature); 327 | if (mid == NULL) { 328 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 329 | return i; 330 | } 331 | va_list args; 332 | va_start(args, strSignature); 333 | i = env->CallIntMethodV(object, mid, args); 334 | va_end(args); 335 | 336 | env->DeleteLocalRef(cls); 337 | return i; 338 | } 339 | 340 | bool JNIHelper::CallBooleanMethod(jobject object, const char *strMethodName, 341 | const char *strSignature, ...) { 342 | bool b; 343 | if (activity_ == NULL) { 344 | LOGI( 345 | "JNIHelper has not been initialized. Call init() to initialize the " 346 | "helper"); 347 | return false; 348 | } 349 | 350 | JNIEnv *env = AttachCurrentThread(); 351 | jclass cls = env->GetObjectClass(object); 352 | jmethodID mid = env->GetMethodID(cls, strMethodName, strSignature); 353 | if (mid == NULL) { 354 | LOGI("method ID %s, '%s' not found", strMethodName, strSignature); 355 | return false; 356 | } 357 | va_list args; 358 | va_start(args, strSignature); 359 | b = env->CallBooleanMethodV(object, mid, args); 360 | va_end(args); 361 | 362 | env->DeleteLocalRef(cls); 363 | return b; 364 | } 365 | 366 | jobject JNIHelper::CreateObject(const char *class_name) { 367 | JNIEnv *env = AttachCurrentThread(); 368 | 369 | jclass cls = env->FindClass(class_name); 370 | jmethodID constructor = env->GetMethodID(cls, "", "()V"); 371 | 372 | jobject obj = env->NewObject(cls, constructor); 373 | jobject objGlobal = env->NewGlobalRef(obj); 374 | env->DeleteLocalRef(obj); 375 | env->DeleteLocalRef(cls); 376 | return objGlobal; 377 | } 378 | 379 | void JNIHelper::RunOnUiThread(std::function callback) { 380 | // Lock mutex 381 | std::lock_guard lock(mutex_); 382 | 383 | JNIEnv *env = AttachCurrentThread(); 384 | static jmethodID mid = NULL; 385 | if (mid == NULL) 386 | mid = env->GetMethodID(jni_helper_java_class_, "runOnUIThread", "(J)V"); 387 | 388 | // Allocate temporary function object to be passed around 389 | std::function *pCallback = new std::function(callback); 390 | env->CallVoidMethod(jni_helper_java_ref_, mid, (int64_t)pCallback); 391 | } 392 | 393 | // This JNI function is invoked from UIThread asynchronously 394 | extern "C" { 395 | JNIEXPORT void Java_com_sample_helper_NDKHelper_RunOnUiThreadHandler( 396 | JNIEnv *env, jobject thiz, int64_t pointer) { 397 | std::function *pCallback = (std::function *)pointer; 398 | (*pCallback)(); 399 | 400 | // Deleting temporary object 401 | delete pCallback; 402 | } 403 | } 404 | 405 | } // namespace ndk_helper 406 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/JNIHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #ifndef NDEBUG 30 | #define LOGV(...) \ 31 | ((void)__android_log_print( \ 32 | ANDROID_LOG_VERBOSE, ndk_helper::JNIHelper::GetInstance()->GetAppName(), \ 33 | __VA_ARGS__)) 34 | 35 | #define LOGI(...) \ 36 | ((void)__android_log_print( \ 37 | ANDROID_LOG_INFO, ndk_helper::JNIHelper::GetInstance()->GetAppName(), \ 38 | __VA_ARGS__)) 39 | #define LOGW(...) \ 40 | ((void)__android_log_print( \ 41 | ANDROID_LOG_WARN, ndk_helper::JNIHelper::GetInstance()->GetAppName(), \ 42 | __VA_ARGS__)) 43 | #define LOGE(...) \ 44 | ((void)__android_log_print( \ 45 | ANDROID_LOG_ERROR, ndk_helper::JNIHelper::GetInstance()->GetAppName(), \ 46 | __VA_ARGS__)) 47 | #else 48 | // Have this option is to confirm there is no timing issues: after development 49 | // enable these set of NULL printf to make sure app behaves normally 50 | #define LOGI(...) (0) 51 | #define LOGV(...) (0) 52 | #define LOGW(...) (0) 53 | #define LOGE(...) (0) 54 | 55 | #endif 56 | namespace ndk_helper { 57 | 58 | class JUIView; 59 | 60 | /****************************************************************** 61 | * Helper functions for JNI calls 62 | * This class wraps JNI calls and provides handy interface calling commonly used 63 | * features 64 | * in Java SDK. 65 | * Such as 66 | * - loading graphics files (e.g. PNG, JPG) 67 | * - character code conversion 68 | * - retrieving system properties which only supported in Java SDK 69 | * 70 | * NOTE: To use this class, add NDKHelper.java as a corresponding helpers in 71 | * Java code 72 | */ 73 | class JNIHelper { 74 | public: 75 | /* 76 | * To load your own Java classes, JNIHelper requires to be initialized with a 77 | * ANativeActivity handle. 78 | * This methods need to be called before any call to the helper class. 79 | * Static member of the class 80 | * 81 | * arguments: 82 | * in: activity, pointer to ANativeActivity. Used internally to set up JNI 83 | * environment 84 | * in: helper_class_name, pointer to Java side helper class name. (e.g. 85 | * "com/sample/helper/NDKHelper" in samples ) 86 | */ 87 | static void Init(ANativeActivity *activity, const char *helper_class_name); 88 | 89 | /* 90 | * Init() that accept so name. 91 | * When using a JUI helper class, Java side requires SO name to initialize JNI 92 | * calls to invoke native callbacks. 93 | * Use this version when using JUI helper. 94 | * 95 | * arguments: 96 | * in: activity, pointer to ANativeActivity. Used internally to set up JNI 97 | * environment 98 | * in: helper_class_name, pointer to Java side helper class name. (e.g. 99 | * "com/sample/helper/NDKHelper" in samples ) 100 | * in: native_soname, pointer to soname of native library. (e.g. 101 | * "NativeActivity" for "libNativeActivity.so" ) 102 | */ 103 | static void Init(ANativeActivity *activity, const char *helper_class_name, 104 | const char *native_soname); 105 | 106 | /* 107 | * Retrieve the singleton object of the helper. 108 | * Static member of the class 109 | 110 | * Methods in the class are designed as thread safe. 111 | */ 112 | static JNIHelper *GetInstance(); 113 | 114 | /* 115 | * Convert string from character code other than UTF-8 116 | * 117 | * arguments: 118 | * in: str, pointer to a string which is encoded other than UTF-8 119 | * in: encoding, pointer to a character encoding string. 120 | * The encoding string can be any valid java.nio.charset.Charset name 121 | * e.g. "UTF-16", "Shift_JIS" 122 | * return: converted input string as an UTF-8 std::string 123 | */ 124 | std::string ConvertString(const char *str, const char *encode); 125 | 126 | /* 127 | * Retrieve string used as nearby connection service ID from 128 | * AndroidManifest.xml file. Entry is: 129 | * 132 | */ 133 | std::string GetNearbyConnectionServiceID(); 134 | 135 | /* 136 | * Retrieves application bundle name 137 | * 138 | * return: pointer to an app name string 139 | * 140 | */ 141 | const char *GetAppName() { return app_bunlde_name_.c_str(); } 142 | 143 | /* 144 | * Retrieves application label 145 | * 146 | * return: pointer to an app label string 147 | * 148 | */ 149 | const char *GetAppLabel() { return app_label_.c_str(); } 150 | 151 | /* 152 | * Execute given function in Java UIThread. 153 | * 154 | * arguments: 155 | * in: pFunction, a pointer to a function to be executed in Java UI Thread. 156 | * Note that the helper function returns immediately without synchronizing a 157 | * function completion. 158 | */ 159 | void RunOnUiThread(std::function callback); 160 | 161 | /* 162 | * Attach current thread 163 | * In Android, the thread doesn't have to be 'Detach' current thread 164 | * as application process is only killed and VM does not shut down 165 | */ 166 | JNIEnv *AttachCurrentThread() { 167 | JNIEnv *env; 168 | if (activity_->vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_4) == JNI_OK) 169 | return env; 170 | activity_->vm->AttachCurrentThread(&env, NULL); 171 | pthread_key_create(reinterpret_cast(activity_), DetachCurrentThreadDtor); 172 | return env; 173 | } 174 | 175 | void DetachCurrentThread() { 176 | activity_->vm->DetachCurrentThread(); 177 | return; 178 | } 179 | 180 | /* 181 | * Decrement a global reference to the object 182 | * arguments: 183 | * in: obj, obj to decrement a global reference 184 | */ 185 | void DeleteObject(jobject obj); 186 | 187 | /* 188 | * Helper methods to call a method in given object 189 | */ 190 | jobject CreateObject(const char *class_name); 191 | jobject CallObjectMethod(jobject object, const char *strMethodName, 192 | const char *strSignature, ...); 193 | void CallVoidMethod(jobject object, const char *strMethodName, 194 | const char *strSignature, ...); 195 | float CallFloatMethod(jobject object, const char *strMethodName, 196 | const char *strSignature, ...); 197 | int32_t CallIntMethod(jobject object, const char *strMethodName, 198 | const char *strSignature, ...); 199 | bool CallBooleanMethod(jobject object, const char *strMethodName, 200 | const char *strSignature, ...); 201 | jclass RetrieveClass(JNIEnv *jni, const char *class_name); 202 | 203 | private: 204 | std::string app_bunlde_name_; 205 | std::string app_label_; 206 | 207 | ANativeActivity *activity_; 208 | jobject jni_helper_java_ref_; 209 | jclass jni_helper_java_class_; 210 | 211 | // mutex for synchronization 212 | // This class uses singleton pattern and can be invoked from multiple threads, 213 | // each methods locks the mutex for a thread safety 214 | mutable std::mutex mutex_; 215 | 216 | JNIHelper(); 217 | ~JNIHelper(); 218 | JNIHelper(const JNIHelper &rhs); 219 | JNIHelper &operator=(const JNIHelper &rhs); 220 | 221 | /* 222 | * Call method in JNIHelper class 223 | */ 224 | jobject CallObjectMethod(const char *strMethodName, const char *strSignature, 225 | ...); 226 | void CallVoidMethod(const char *strMethodName, const char *strSignature, ...); 227 | 228 | /* 229 | * Unregister this thread from the VM 230 | */ 231 | static void DetachCurrentThreadDtor(void *p) { 232 | LOGI("detached current thread"); 233 | ANativeActivity *activity = reinterpret_cast (p); 234 | activity->vm->DetachCurrentThread(); 235 | } 236 | }; 237 | 238 | extern "C" { 239 | JNIEXPORT void Java_com_sample_helper_NDKHelper_RunOnUiThreadHandler( 240 | JNIEnv *env, jobject thiz, int64_t pointer); 241 | } 242 | 243 | } // namespace ndk_helper 244 | -------------------------------------------------------------------------------- /app/src/main/cpp/ndk_helper/NDKHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #ifndef _GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_NDKHELPER_H_ 17 | #define _GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_NDKHELPER_H_ 18 | 19 | /****************************************************************** 20 | * NDK support helpers 21 | * Utility module to provide misc functionalities that is used widely in native 22 | * applications, 23 | * such as gesture detection, jni bridge, openGL context etc. 24 | * 25 | * The purpose of this module is, 26 | * - Provide best practices using NDK 27 | * - Provide handy utility functions for NDK development 28 | * - Make NDK samples more simpler and readable 29 | */ 30 | #include "gl3stub.h" // GLES3 stubs 31 | #include "GLContext.h" // EGL & OpenGL manager 32 | #include "JNIHelper.h" // JNI support 33 | #endif // GOOGLE_SAMPLES_ANDROID_NEARBYCONNECTIONS_JNI_NDK_HELPER_NDKHELPER_H_ 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/example/games/nearbyconnections/NearbyNativeActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.example.games.nearbyconnections; 17 | 18 | import android.Manifest; 19 | import android.annotation.SuppressLint; 20 | import android.app.Activity; 21 | import android.app.NativeActivity; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.content.pm.PackageManager; 25 | import android.os.Bundle; 26 | import android.support.annotation.NonNull; 27 | import android.support.v4.content.ContextCompat; 28 | import android.view.View; 29 | import android.widget.Toast; 30 | 31 | public class NearbyNativeActivity extends NativeActivity { 32 | private static final int REQUEST_CODE_REQUIRED_PERMISSIONS = 5444; 33 | 34 | // Load SO 35 | static { 36 | System.loadLibrary("NearbyNativeActivity"); 37 | } 38 | 39 | /** 40 | * These permissions are required before connecting to Nearby Connections. Only {@link 41 | * Manifest.permission#ACCESS_COARSE_LOCATION} is considered dangerous, so the others should be 42 | * granted just by having them in our AndroidManfiest.xml 43 | */ 44 | private static final String[] REQUIRED_PERMISSIONS = 45 | new String[] { 46 | Manifest.permission.BLUETOOTH, 47 | Manifest.permission.BLUETOOTH_ADMIN, 48 | Manifest.permission.ACCESS_WIFI_STATE, 49 | Manifest.permission.CHANGE_WIFI_STATE, 50 | Manifest.permission.ACCESS_COARSE_LOCATION, 51 | }; 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | // Hide toolbar 57 | int SDK_INT = android.os.Build.VERSION.SDK_INT; 58 | if (SDK_INT >= 19) { 59 | setImmersiveSticky(); 60 | 61 | View decorView = getWindow().getDecorView(); 62 | decorView 63 | .setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { 64 | @Override 65 | public void onSystemUiVisibilityChange(int visibility) { 66 | setImmersiveSticky(); 67 | } 68 | }); 69 | } 70 | 71 | nativeOnActivityCreated(this, savedInstanceState); 72 | } 73 | 74 | @Override 75 | @SuppressLint("InlinedApi") 76 | @SuppressWarnings("deprecation") 77 | protected void onResume() { 78 | super.onResume(); 79 | 80 | // Hide toolbar 81 | int SDK_INT = android.os.Build.VERSION.SDK_INT; 82 | if (SDK_INT >= 14 && SDK_INT < 19) { 83 | getWindow().getDecorView().setSystemUiVisibility( 84 | View.SYSTEM_UI_FLAG_FULLSCREEN 85 | | View.SYSTEM_UI_FLAG_LOW_PROFILE); 86 | } else if (SDK_INT >= 19) { 87 | setImmersiveSticky(); 88 | } 89 | 90 | nativeOnActivityResumed(this); 91 | } 92 | 93 | @SuppressLint("InlinedApi") 94 | void setImmersiveSticky() { 95 | View decorView = getWindow().getDecorView(); 96 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN 97 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 98 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 99 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 100 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 101 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 102 | } 103 | 104 | @Override 105 | protected void onPause() { 106 | super.onPause(); 107 | // This call is to suppress 'E/WindowManager(): 108 | // android.view.WindowLeaked...' errors. 109 | // Since orientation change events in NativeActivity comes later than 110 | // expected, we can not dismiss 111 | // popupWindow gracefully from NativeActivity. 112 | // So we are releasing popupWindows explicitly triggered from Java 113 | // callback through JNI call. 114 | OnPauseHandler(); 115 | 116 | nativeOnActivityPaused(this); 117 | } 118 | 119 | @Override 120 | protected void onDestroy() { 121 | super.onDestroy(); 122 | nativeOnActivityDestroyed(this); 123 | } 124 | 125 | @Override 126 | protected void onStart() { 127 | super.onStart(); 128 | if (!hasPermissions(this, REQUIRED_PERMISSIONS)) { 129 | requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS); 130 | } 131 | nativeOnActivityStarted(this); 132 | } 133 | 134 | @Override 135 | protected void onStop() { 136 | super.onStop(); 137 | nativeOnActivityStopped(this); 138 | } 139 | 140 | @Override 141 | protected void onSaveInstanceState(Bundle outState) { 142 | super.onSaveInstanceState(outState); 143 | nativeOnActivitySaveInstanceState(this, outState); 144 | } 145 | 146 | @Override 147 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 148 | nativeOnActivityResult(this, requestCode,resultCode, data); 149 | } 150 | 151 | public void onRequestPermissionsResult( 152 | int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 153 | if (requestCode == REQUEST_CODE_REQUIRED_PERMISSIONS) { 154 | for (int grantResult : grantResults) { 155 | if (grantResult == PackageManager.PERMISSION_DENIED) { 156 | Toast.makeText(this, R.string.error_missing_permissions, Toast.LENGTH_LONG).show(); 157 | finish(); 158 | return; 159 | } 160 | } 161 | recreate(); 162 | } 163 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 164 | } 165 | // Implemented in C++. 166 | public static native void nativeOnActivityResult(Activity activity, 167 | int requestCode, int resultCode, Intent data); 168 | 169 | public static native void nativeOnActivityCreated(Activity activity, 170 | Bundle savedInstanceState); 171 | 172 | private static native void nativeOnActivityDestroyed(Activity activity); 173 | 174 | private static native void nativeOnActivityPaused(Activity activity); 175 | 176 | private static native void nativeOnActivityResumed(Activity activity); 177 | 178 | private static native void nativeOnActivitySaveInstanceState( 179 | Activity activity, Bundle outState); 180 | 181 | private static native void nativeOnActivityStarted(Activity activity); 182 | 183 | private static native void nativeOnActivityStopped(Activity activity); 184 | 185 | native public void OnPauseHandler(); 186 | 187 | /** @return True if the app was granted all the permissions. False otherwise. */ 188 | public static boolean hasPermissions(Context context, String... permissions) { 189 | for (String permission : permissions) { 190 | if (ContextCompat.checkSelfPermission(context, permission) 191 | != PackageManager.PERMISSION_GRANTED) { 192 | return false; 193 | } 194 | } 195 | return true; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/JUIButton.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.graphics.drawable.Drawable; 21 | import android.support.v7.widget.AppCompatButton; 22 | import android.util.AttributeSet; 23 | import android.util.Log; 24 | import android.view.MotionEvent; 25 | import android.view.View; 26 | 27 | // 28 | //Java UI SeekBar implementation 29 | // 30 | public class JUIButton extends AppCompatButton { 31 | private JUIForwardingPopupWindow dummyPopupWindow; 32 | 33 | public JUIForwardingPopupWindow getDummyWindow() { 34 | return dummyPopupWindow; 35 | } 36 | 37 | public JUIButton(Context context) { 38 | this(context, null, android.R.attr.buttonStyle); 39 | } 40 | 41 | public JUIButton(Context context, AttributeSet attrs) { 42 | this(context, attrs, android.R.attr.buttonStyle); 43 | } 44 | 45 | public JUIButton(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | 48 | final Drawable d = getBackground(); 49 | 50 | setOnTouchListener(new View.OnTouchListener() { 51 | 52 | @Override 53 | public boolean onTouch(View v, MotionEvent event) { 54 | Log.i("test", "clicked, action" + event.getAction()); 55 | switch (event.getAction()) { 56 | case MotionEvent.ACTION_DOWN: 57 | // setPressed(true); 58 | d.setState(PRESSED_ENABLED_STATE_SET); 59 | 60 | JUIHelper.JUICallbackHandler(getId(), 61 | JUIHelper.JUICALLBACK_BUTTON_DOWN, 0, 0); 62 | return true; 63 | // break; 64 | case MotionEvent.ACTION_CANCEL: 65 | JUIHelper.JUICallbackHandler(getId(), 66 | JUIHelper.JUICALLBACK_BUTTON_CANCEL, 0, 0); 67 | return true; 68 | case MotionEvent.ACTION_UP: 69 | JUIHelper.JUICallbackHandler(getId(), 70 | JUIHelper.JUICALLBACK_BUTTON_UP, 0, 0); 71 | d.setState(ENABLED_STATE_SET); 72 | 73 | // setPressed(false); 74 | // break; 75 | return true; 76 | case MotionEvent.ACTION_MOVE: 77 | d.setState(PRESSED_ENABLED_STATE_SET); 78 | return true; 79 | 80 | } 81 | 82 | return false; 83 | } 84 | }); 85 | 86 | dummyPopupWindow = new JUIForwardingPopupWindow((Activity)context, this); 87 | } 88 | 89 | @Override 90 | protected void onLayout(boolean changed, int left, int top, int right, 91 | int bottom) { 92 | super.onLayout(changed, left, top, right, bottom); 93 | 94 | // Put dummy popupWindow over the control 95 | // so that relativeLayout can pass through touch events to native 96 | // activity for a background area 97 | if (changed) { 98 | dummyPopupWindow.update(this); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/JUIDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import android.view.View; 19 | import android.widget.RelativeLayout; 20 | import android.annotation.SuppressLint; 21 | import android.app.Dialog; 22 | import android.app.NativeActivity; 23 | import android.content.DialogInterface; 24 | 25 | /* 26 | * Java UI Dialog implementation 27 | */ 28 | public class JUIDialog extends Dialog { 29 | 30 | NativeActivity activity_; 31 | RelativeLayout dialogRelativeLayout_; 32 | int id_ = 0; 33 | 34 | public JUIDialog(final NativeActivity act) { 35 | super(act); 36 | activity_ = act; 37 | 38 | // Setup relative layout 39 | dialogRelativeLayout_ = new RelativeLayout(activity_); 40 | setContentView(dialogRelativeLayout_); 41 | 42 | setOnDismissListener(new OnDismissListener() { 43 | @Override 44 | public void onDismiss(DialogInterface dialog) { 45 | NDKHelper.checkSOLoaded(); 46 | if (id_ != 0) 47 | JUIHelper.JUICallbackHandler(id_, 48 | JUIHelper.JUICALLBACK_DIALOG_DISMISSED, 0, 0); 49 | } 50 | }); 51 | 52 | setOnCancelListener(new OnCancelListener() { 53 | @Override 54 | public void onCancel(DialogInterface dialog) { 55 | NDKHelper.checkSOLoaded(); 56 | if (id_ != 0) 57 | JUIHelper.JUICallbackHandler(id_, 58 | JUIHelper.JUICALLBACK_DIALOG_CANCELLED, 0, 0); 59 | } 60 | }); 61 | } 62 | 63 | public void setID(int id) { 64 | id_ = id; 65 | } 66 | 67 | @SuppressLint("NewApi") 68 | public void addView(final View view) { 69 | activity_.runOnUiThread(new Runnable() { 70 | @Override 71 | public void run() { 72 | if (dialogRelativeLayout_ != null) 73 | dialogRelativeLayout_.addView(view); 74 | } 75 | }); 76 | return; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/JUIForwardingPopupWindow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import android.app.Activity; 19 | import android.app.NativeActivity; 20 | import android.content.Context; 21 | import android.util.AttributeSet; 22 | import android.view.Gravity; 23 | import android.view.MotionEvent; 24 | import android.view.View; 25 | import android.view.Window; 26 | import android.widget.LinearLayout; 27 | import android.widget.PopupWindow; 28 | 29 | /* 30 | * Popup window that forward user inputs to underlying widgets 31 | */ 32 | class JUIForwardingLayout extends LinearLayout { 33 | View target; 34 | 35 | public JUIForwardingLayout(Context context) { 36 | super(context); 37 | } 38 | 39 | public JUIForwardingLayout(Context context, AttributeSet attrs) { 40 | super(context, attrs); 41 | } 42 | 43 | public void setTarget(View view) { 44 | target = view; 45 | } 46 | 47 | @Override 48 | public boolean onTouchEvent(MotionEvent ev) { 49 | if (target != null) 50 | target.dispatchTouchEvent(ev); 51 | return true; 52 | } 53 | } 54 | 55 | public class JUIForwardingPopupWindow extends PopupWindow { 56 | JUIForwardingPopupWindow(final Activity activity, final View view) { 57 | super(view.getWidth(), view.getHeight()); 58 | 59 | JUIForwardingLayout dummyRelativeLayout = new JUIForwardingLayout( 60 | activity); 61 | dummyRelativeLayout.setTarget(view); 62 | setContentView(dummyRelativeLayout); 63 | 64 | activity.runOnUiThread(new Runnable() { 65 | @Override 66 | public void run() { 67 | Window window = activity.getWindow(); 68 | if( window != null ) 69 | { 70 | View decor = window.getDecorView(); 71 | if( decor != null ) 72 | { 73 | showAtLocation(decor, Gravity.TOP 74 | | Gravity.START, view.getLeft(), view.getTop()); 75 | } 76 | } 77 | 78 | } 79 | }); 80 | } 81 | 82 | void update(View view) { 83 | int[] location = new int[2]; 84 | view.getLocationInWindow(location); 85 | super.update(location[0], location[1], view.getWidth(), 86 | view.getHeight()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/JUIHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import java.lang.reflect.Constructor; 19 | import java.lang.reflect.Method; 20 | import java.util.LinkedList; 21 | 22 | import android.annotation.TargetApi; 23 | import android.app.Dialog; 24 | import android.app.NativeActivity; 25 | import android.content.pm.ActivityInfo; 26 | import android.content.pm.PackageManager.NameNotFoundException; 27 | import android.os.Build; 28 | import android.util.Log; 29 | import android.view.Gravity; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | import android.view.ViewGroup.MarginLayoutParams; 33 | import android.view.Window; 34 | import android.view.WindowManager.LayoutParams; 35 | import android.widget.LinearLayout; 36 | import android.widget.PopupWindow; 37 | import android.widget.RadioGroup; 38 | import android.widget.RelativeLayout; 39 | 40 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 41 | public class JUIHelper { 42 | 43 | public JUIHelper(NativeActivity act) { 44 | activity_ = act; 45 | } 46 | 47 | private NativeActivity activity_; 48 | 49 | // 50 | // JUI related helpers 51 | // 52 | LinkedList dummyWindows_ = new LinkedList(); 53 | RelativeLayout JUIRelativeLayout_; 54 | public static final int JUICALLBACK_SEEKBAR_STOP_TRACKING_TOUCH = 1; 55 | public static final int JUICALLBACK_SEEKBAR_START_TRACKING_TOUCH = 2; 56 | public static final int JUICALLBACK_SEEKBAR_PROGRESSCHANGED = 3; 57 | public static final int JUICALLBACK_COMPOUNDBUTTON_CHECKEDCHANGED = 4; 58 | public static final int JUICALLBACK_BUTTON_DOWN = 5; 59 | public static final int JUICALLBACK_BUTTON_UP = 6; 60 | public static final int JUICALLBACK_BUTTON_CANCEL = 7; 61 | public static final int JUICALLBACK_DIALOG_DISMISSED = 108; 62 | public static final int JUICALLBACK_DIALOG_CANCELLED = 109; 63 | 64 | native static public void JUICallbackHandler(int id, int message, 65 | int param1, int pram2); 66 | 67 | private void initializeWidget(final View view) { 68 | activity_.runOnUiThread(new Runnable() { 69 | @Override 70 | public void run() { 71 | // Check if the control has dummy popupwindow to forward user 72 | // inputs 73 | try { 74 | Method method = view.getClass().getMethod("getDummyWindow"); 75 | JUIForwardingPopupWindow window = (JUIForwardingPopupWindow) method 76 | .invoke(view); 77 | if (window != null) 78 | dummyWindows_.add(window); 79 | } catch (NoSuchMethodException e) { 80 | return; 81 | } catch (Exception e) { 82 | return; 83 | } 84 | 85 | // Set layoutParameter for Widget 86 | RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams( 87 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 88 | view.setLayoutParams(relativeParams); 89 | } 90 | }); 91 | } 92 | 93 | public View createWidget(String className, int id) { 94 | View view; 95 | try { 96 | String baseName = getClass().getName().substring(0, 97 | getClass().getName().lastIndexOf(".") + 1); 98 | @SuppressWarnings("rawtypes") 99 | Class cls = Class.forName(baseName + className); 100 | @SuppressWarnings("unchecked") 101 | Constructor ctor = cls.getConstructor(NativeActivity.class); 102 | view = ctor.newInstance(activity_); 103 | view.setId(id); 104 | } catch (Exception e) { 105 | Log.e("NDKHelper", "Could not find the name"); 106 | return null; 107 | } 108 | 109 | initializeWidget(view); 110 | return view; 111 | } 112 | 113 | public View createWidget(String className, int id, int param) { 114 | View view; 115 | try { 116 | String baseName = getClass().getName().substring(0, 117 | getClass().getName().lastIndexOf(".") + 1); 118 | @SuppressWarnings("rawtypes") 119 | Class cls = Class.forName(baseName + className); 120 | @SuppressWarnings("unchecked") 121 | Constructor ctor = cls.getConstructor(NativeActivity.class, 122 | int.class); 123 | view = ctor.newInstance(activity_, param); 124 | view.setId(id); 125 | } catch (Exception e) { 126 | return null; 127 | } 128 | 129 | initializeWidget(view); 130 | return view; 131 | } 132 | 133 | public void closeWidget(final View view) { 134 | JUIForwardingPopupWindow window = null; 135 | // Check if the control has dummy popupwindow to forward user inputs 136 | try { 137 | Method method = view.getClass().getMethod("getDummyWindow"); 138 | window = (JUIForwardingPopupWindow) method.invoke(view); 139 | } catch (Exception e) { 140 | } 141 | 142 | final PopupWindow closingWindow = window; 143 | activity_.runOnUiThread(new Runnable() { 144 | @Override 145 | public void run() { 146 | if (closingWindow != null) { 147 | dummyWindows_.remove(closingWindow); 148 | closingWindow.dismiss(); 149 | } 150 | JUIRelativeLayout_.removeView(view); 151 | } 152 | }); 153 | return; 154 | } 155 | 156 | public void addRule(final View view, final int param1, final int param2) { 157 | activity_.runOnUiThread(new Runnable() { 158 | @Override 159 | public void run() { 160 | RelativeLayout.LayoutParams relativeParams = (RelativeLayout.LayoutParams) view 161 | .getLayoutParams(); 162 | relativeParams.addRule(param1, param2); 163 | } 164 | }); 165 | } 166 | 167 | public void setLayoutParams(final View view, final int width, 168 | final int height) { 169 | activity_.runOnUiThread(new Runnable() { 170 | @Override 171 | public void run() { 172 | ViewGroup.LayoutParams params = view.getLayoutParams(); 173 | params.width = width; 174 | params.height = height; 175 | } 176 | }); 177 | } 178 | 179 | public void setMargins(final View view, final int left, final int top, 180 | final int right, final int bottom) { 181 | activity_.runOnUiThread(new Runnable() { 182 | @Override 183 | public void run() { 184 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view 185 | .getLayoutParams(); 186 | params.setMargins(left, top, right, bottom); 187 | } 188 | }); 189 | } 190 | 191 | public void setLayoutParams(final View view, final int width, 192 | final int height, final float weight) { 193 | activity_.runOnUiThread(new Runnable() { 194 | @Override 195 | public void run() { 196 | ViewGroup.LayoutParams params = view.getLayoutParams(); 197 | 198 | if (params instanceof LinearLayout.LayoutParams) { 199 | LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) params; 200 | layoutParams.width = width; 201 | layoutParams.height = height; 202 | layoutParams.weight = weight; 203 | } else { 204 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 205 | width, height, weight); 206 | view.setLayoutParams(layoutParams); 207 | } 208 | } 209 | }); 210 | } 211 | 212 | public void addView(final View view) { 213 | activity_.runOnUiThread(new Runnable() { 214 | @Override 215 | public void run() { 216 | if (JUIRelativeLayout_ != null) { 217 | JUIRelativeLayout_.addView(view); 218 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view 219 | .getLayoutParams(); 220 | 221 | if (params instanceof RelativeLayout.LayoutParams == false) { 222 | // Switching to relative layout param 223 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( 224 | params.width, params.height); 225 | layoutParams.leftMargin = params.leftMargin; 226 | layoutParams.bottomMargin = params.bottomMargin; 227 | layoutParams.rightMargin = params.rightMargin; 228 | layoutParams.topMargin = params.topMargin; 229 | view.setLayoutParams(layoutParams); 230 | } 231 | } 232 | } 233 | }); 234 | return; 235 | } 236 | 237 | public void addView(final ViewGroup layout, final View view) { 238 | activity_.runOnUiThread(new Runnable() { 239 | @Override 240 | public void run() { 241 | layout.addView(view); 242 | 243 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view 244 | .getLayoutParams(); 245 | if (layout instanceof RadioGroup) { 246 | if (params instanceof RadioGroup.LayoutParams == false) { 247 | // Switching to linear layout param 248 | RadioGroup.LayoutParams layoutParams = new RadioGroup.LayoutParams( 249 | params.width, params.height); 250 | layoutParams.leftMargin = params.leftMargin; 251 | layoutParams.bottomMargin = params.bottomMargin; 252 | layoutParams.rightMargin = params.rightMargin; 253 | layoutParams.topMargin = params.topMargin; 254 | view.setLayoutParams(layoutParams); 255 | } 256 | } else if (layout instanceof LinearLayout) { 257 | if (params instanceof LinearLayout.LayoutParams == false) { 258 | // Switching to linear layout param 259 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 260 | params.width, params.height); 261 | layoutParams.leftMargin = params.leftMargin; 262 | layoutParams.bottomMargin = params.bottomMargin; 263 | layoutParams.rightMargin = params.rightMargin; 264 | layoutParams.topMargin = params.topMargin; 265 | view.setLayoutParams(layoutParams); 266 | } 267 | } else if (layout instanceof RelativeLayout) { 268 | if (params instanceof RelativeLayout.LayoutParams == false) { 269 | // Switching to linear layout param 270 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( 271 | params.width, params.height); 272 | layoutParams.leftMargin = params.leftMargin; 273 | layoutParams.bottomMargin = params.bottomMargin; 274 | layoutParams.rightMargin = params.rightMargin; 275 | layoutParams.topMargin = params.topMargin; 276 | view.setLayoutParams(layoutParams); 277 | } 278 | } 279 | } 280 | }); 281 | return; 282 | } 283 | 284 | // 285 | // Create PopupWindow over nativeActivity with RelativeLayout 286 | // 287 | public PopupWindow createPopupWindow(final NativeActivity act) { 288 | // Check manifest settings if the activity wouldn't be destroyed when 289 | // the device orientation changes 290 | try { 291 | ActivityInfo info = act.getPackageManager().getActivityInfo( 292 | act.getComponentName(), 0); 293 | if ((info.configChanges & ActivityInfo.CONFIG_ORIENTATION) == 0 294 | || (info.configChanges & ActivityInfo.CONFIG_SCREEN_SIZE) == 0) { 295 | Log.i("NDKHelper", 296 | "Activity does not have android:configChanges='orientation|screenSize' attributes in AndroidManifest.xml."); 297 | } 298 | } catch (NameNotFoundException e) { 299 | Log.e("NDKHelper", "Failed to find ActivityName"); 300 | } 301 | 302 | activity_ = act; 303 | // activity.setTheme(android.R.style.Theme_DeviceDefault); 304 | 305 | final PopupWindow popupWindow = new PopupWindow( 306 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 307 | 308 | activity_.runOnUiThread(new Runnable() { 309 | @Override 310 | public void run() { 311 | Window window = activity_.getWindow(); 312 | if( window != null ) 313 | { 314 | View decorView = window.getDecorView(); 315 | if( decorView == null ) 316 | { 317 | // Put dummy layout to NativeActivity 318 | LinearLayout mainLayout = new LinearLayout(activity_); 319 | MarginLayoutParams params = new MarginLayoutParams( 320 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 321 | params.setMargins(0, 0, 0, 0); 322 | activity_.setContentView(mainLayout, params); 323 | decorView = mainLayout; 324 | } 325 | 326 | // Setup relative layout 327 | JUIRelativeLayout_ = new RelativeLayout(activity_); 328 | popupWindow.setContentView(JUIRelativeLayout_); 329 | 330 | // Show our UI over NativeActivity window 331 | popupWindow.showAtLocation(decorView, Gravity.TOP 332 | | Gravity.START, 0, 0); 333 | popupWindow.setTouchable(false); 334 | popupWindow.update(); 335 | } 336 | } 337 | }); 338 | return popupWindow; 339 | } 340 | 341 | public void suspendPopupWindow(final PopupWindow window) { 342 | activity_.runOnUiThread(new Runnable() { 343 | @Override 344 | public void run() { 345 | // Dismiss dummy windows 346 | for (PopupWindow w : dummyWindows_) { 347 | w.dismiss(); 348 | } 349 | dummyWindows_.clear(); 350 | window.dismiss(); 351 | if (dialog_ != null) 352 | dialog_.dismiss(); 353 | 354 | JUIRelativeLayout_ = null; 355 | } 356 | }); 357 | return; 358 | } 359 | 360 | public void resumePopupWindow(NativeActivity act, final PopupWindow p) { 361 | activity_ = act; 362 | if(p.isShowing()) { 363 | Log.i("JUIHelper::", "ResumePopupWindow is to about to show"); 364 | } 365 | return; 366 | } 367 | 368 | public void closePopupWindow(final PopupWindow window) { 369 | activity_.runOnUiThread(new Runnable() { 370 | @Override 371 | public void run() { 372 | // Dismiss dummy windows 373 | for (PopupWindow w : dummyWindows_) { 374 | w.dismiss(); 375 | } 376 | dummyWindows_.clear(); 377 | window.dismiss(); 378 | 379 | if (dialog_ != null) 380 | dialog_.dismiss(); 381 | } 382 | }); 383 | return; 384 | } 385 | 386 | /* 387 | * Dialog helpers 388 | */ 389 | Dialog dialog_; 390 | 391 | public Object createDialog(final NativeActivity act) { 392 | JUIDialog dlg = new JUIDialog(act); 393 | dialog_ = dlg; 394 | 395 | return dlg; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/JUITextView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import android.content.Context; 19 | import android.support.v7.widget.AppCompatTextView; 20 | import android.util.AttributeSet; 21 | 22 | 23 | /* 24 | * Java UI TextView implementation 25 | */ 26 | public class JUITextView extends AppCompatTextView { 27 | 28 | public JUITextView(Context context) { 29 | super(context); 30 | } 31 | 32 | public JUITextView(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | public JUITextView(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | } 39 | 40 | public JUIForwardingPopupWindow getDummyWindow() { 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/sample/helper/NDKHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sample.helper; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.NativeActivity; 20 | import android.content.pm.ApplicationInfo; 21 | import android.content.pm.PackageManager; 22 | import android.content.pm.PackageManager.NameNotFoundException; 23 | import android.os.Build; 24 | import android.os.Bundle; 25 | import android.util.Log; 26 | 27 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 28 | public class NDKHelper { 29 | 30 | public NDKHelper(NativeActivity act) { 31 | activity = act; 32 | } 33 | 34 | public void loadLibrary(String soname) { 35 | if (soname.isEmpty() == false) { 36 | System.loadLibrary(soname); 37 | loadedSO = true; 38 | } 39 | } 40 | 41 | public static Boolean checkSOLoaded() { 42 | if (loadedSO == false) { 43 | Log.e("NDKHelper", 44 | "--------------------------------------------\n" 45 | + ".so has not been loaded. To use JUI helper, please initialize with \n" 46 | + "NDKHelper::Init( ANativeActivity* activity, const char* helper_class_name, const char* native_soname);\n" 47 | + "--------------------------------------------\n"); 48 | return false; 49 | } else 50 | return true; 51 | } 52 | 53 | private static boolean loadedSO = false; 54 | NativeActivity activity; 55 | 56 | public String getApplicationName() { 57 | final PackageManager pm = activity.getPackageManager(); 58 | ApplicationInfo ai; 59 | try { 60 | ai = pm.getApplicationInfo(activity.getPackageName(), 0); 61 | } catch (final NameNotFoundException e) { 62 | ai = null; 63 | } 64 | String applicationName = (String) (ai != null ? pm 65 | .getApplicationLabel(ai) : "(unknown)"); 66 | return applicationName; 67 | } 68 | 69 | public String getNearbyConnectionServiceID() { 70 | String nb_id = "unkown_id"; 71 | try { 72 | ApplicationInfo ai = activity.getPackageManager().getApplicationInfo(activity.getPackageName(), PackageManager.GET_META_DATA); 73 | Bundle bundle = ai.metaData; 74 | nb_id = bundle.getString("com.google.android.gms.nearby.connection.SERVICE_ID"); 75 | } catch (NameNotFoundException e) { 76 | Log.e("NDKHelper", "Failed to load meta-data, NameNotFound: " + e.getMessage()); 77 | } catch (NullPointerException e) { 78 | Log.e("NDKHelper", "Failed to load meta-data, NullPointer: " + e.getMessage()); 79 | } 80 | return nb_id; 81 | } 82 | /* 83 | * Helper to execute function in UIThread 84 | */ 85 | public void runOnUIThread(final long p) { 86 | if (checkSOLoaded()) { 87 | activity.runOnUiThread(new Runnable() { 88 | @Override 89 | public void run() { 90 | RunOnUiThreadHandler(p); 91 | } 92 | }); 93 | } 94 | return; 95 | } 96 | 97 | /* 98 | * Native code helper for RunOnUiThread 99 | */ 100 | native public void RunOnUiThreadHandler(long pointer); 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #3F51B5 19 | #303F9F 20 | #FF4081 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | NearbyConnections Sample 18 | google-nearby-connection-native-demo 19 | Missing Permissions 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 (C) Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Top-level build file where you can add configuration options common to all 18 | // sub-projects/modules. 19 | 20 | buildscript { 21 | repositories { 22 | google() 23 | jcenter() 24 | } 25 | dependencies { 26 | classpath 'com.android.tools.build:gradle:3.0.1' 27 | } 28 | } 29 | 30 | allprojects { 31 | repositories { 32 | google() 33 | jcenter() 34 | } 35 | 36 | // Depend on another project that downloads and unzips the C++ SDK. 37 | evaluationDependsOn(':gpg-sdk') 38 | 39 | // As the plugin has created the build tasks, add the gpg-sdk task to 40 | // the task dependencies so it gets done before compiling 41 | tasks.whenTaskAdded { 42 | task-> if (!task.name.endsWith("clean")) project(':gpg-sdk').defaultTasks.each { 43 | t->task.dependsOn project(':gpg-sdk').tasks[t] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gpg-sdk/build.gradle: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018 Google LLC 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | /* 16 | sub module to download the Google Play games 17 | */ 18 | project.ext { 19 | if (!project.hasProperty('gpg_sdk_link')) { 20 | gpg_sdk_link = System.getenv("GPG_SDK_LINK") 21 | if (gpg_sdk_link == null || gpg_sdk_link.isEmpty()) { 22 | gpg_sdk_link = 'https://developers.google.com/games/services/downloads/gpg-cpp-sdk.v3.0.zip' 23 | } 24 | } 25 | } 26 | task download_and_stage_gpg_sdk(dependsOn:'unzip_gpg_sdk') { 27 | 28 | } 29 | 30 | task fetch_gpg_cpp_sdk () { 31 | 32 | doFirst { 33 | ant.get(src: gpg_sdk_link, dest: 'gpg_cpp_sdk.zip', skipexisting: 'true') 34 | } 35 | doLast { 36 | println "Got ${gpg_sdk_link} as gpg_cpp_sdk.zip" 37 | } 38 | outputs.files('gpg_cpp_sdk.zip') 39 | outputs.upToDateWhen {file('gpg_cpp_sdk.zip').exists()} 40 | } 41 | fetch_gpg_cpp_sdk.description = "Download the gpg sdk from the specified location" 42 | 43 | task unzip_gpg_sdk() { 44 | def dest = file( "${project.projectDir}/gpg-cpp-sdk") 45 | doFirst { 46 | if (!dest.exists()) { 47 | copy { 48 | from(zipTree('gpg_cpp_sdk.zip')) { 49 | include 'gpg-cpp-sdk/android/**/*' 50 | } 51 | 52 | into { project.projectDir } 53 | } 54 | } else { 55 | println "Skipping sdk unzipping, ${dest.absolutePath} exists" 56 | } 57 | } 58 | outputs.upToDateWhen {file("${dest.absolutePath}/android").exists() && 59 | file('gpg-cpp-sdk/android/lib/c++/arm64-v8a/libgpg.a').exists()} 60 | description = "Unzips the GPG SDK into the correct dir for NDK" 61 | dependsOn fetch_gpg_cpp_sdk 62 | } 63 | 64 | task clean() { 65 | doFirst { 66 | file('gpg_cpp_sdk.zip').delete() 67 | } 68 | doLast { 69 | delete 'gpg-cpp-sdk' 70 | delete 'build' 71 | } 72 | } 73 | 74 | defaultTasks = ['unzip_gpg_sdk'] 75 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-nearby-cpp/2205e3cd1a217812d8cda5690a8e7b1b7068b388/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 14 14:20:53 PST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':gpg-sdk' 2 | --------------------------------------------------------------------------------