1 /*
2 * Copyright 2018 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
17 #include <sstream>
18 #include <string>
19 #include <iostream>
20 #include <jni.h>
21
22 #include "modp_b64.h"
23
24 #define LOG_TAG "TuningFork.Clearcut"
25 #include "Log.h"
26
27 #include "clearcut_backend.h"
28 #include "clearcutserializer.h"
29 #include "uploadthread.h"
30 #include "tuningfork/protobuf_nano_util.h"
31 #include "tuningfork_internal.h"
32
33 namespace tuningfork {
34
35 static char s_clearcut_log_source[] = "TUNING_FORK";
36
~ClearcutBackend()37 ClearcutBackend::~ClearcutBackend() {}
38
Process(const ProtobufSerialization & evt_ser)39 bool ClearcutBackend::Process(const ProtobufSerialization &evt_ser) {
40
41 ALOGI("Process log");
42
43 if(proto_print_ != nullptr)
44 proto_print_->Print(evt_ser);
45
46 JNIEnv* env;
47 //Attach thread
48 int envStatus = vm_->GetEnv((void**)&env, JNI_VERSION_1_6);
49
50 switch(envStatus) {
51 case JNI_OK:
52 break;
53 case JNI_EVERSION:
54 ALOGW("JNI Version is not supported, status : %d", envStatus);
55 return false;
56 case JNI_EDETACHED: {
57 int attachStatus = vm_->AttachCurrentThread(&env, (void *) NULL);
58 if (attachStatus != JNI_OK) {
59 ALOGW("Thread is not attached, status : %d", attachStatus);
60 return false;
61 }
62 }
63 break;
64 default:
65 ALOGW("JNIEnv is not OK, status : %d", envStatus);
66 return false;
67 }
68
69 //Cast to jbytearray
70 jsize length = evt_ser.size();
71 jbyteArray output = env->NewByteArray(length);
72 env->SetByteArrayRegion(output, 0, length, reinterpret_cast<const jbyte *>(evt_ser.data()));
73
74 //Send to Clearcut
75 jobject newBuilder = env->CallObjectMethod(clearcut_logger_, new_event_, output);
76 env->CallVoidMethod(newBuilder, log_method_);
77 bool hasException = CheckException(env);
78
79 // Detach thread.
80 vm_->DetachCurrentThread();
81 ALOGI("Message was sent to clearcut");
82 return !hasException;
83 }
84
Init(JNIEnv * env,jobject activity,ProtoPrint * proto_print)85 bool ClearcutBackend::Init(JNIEnv *env, jobject activity, ProtoPrint* proto_print) {
86 ALOGI("%s", "Start clearcut initialization...");
87
88 proto_print_ = proto_print;
89 env->GetJavaVM(&vm_);
90 if(vm_ == nullptr) {
91 ALOGE("%s", "JavaVM is null...");
92 return false;
93 }
94
95 try {
96 bool inited = InitWithClearcut(env, activity, false);
97 ALOGI("Clearcut status: %s available", inited ? "" : "not");
98 return inited;
99 } catch (const std::exception& e) {
100 ALOGI("Clearcut status: not available");
101 return false;
102 }
103
104 }
105
IsGooglePlayServiceAvailable(JNIEnv * env,jobject context)106 bool ClearcutBackend::IsGooglePlayServiceAvailable(JNIEnv* env, jobject context) {
107 jclass availabilityClass =
108 env->FindClass("com/google/android/gms/common/GoogleApiAvailability");
109 if(CheckException(env)) return false;
110
111 jmethodID getInstanceMethod = env->GetStaticMethodID(
112 availabilityClass,
113 "getInstance",
114 "()Lcom/google/android/gms/common/GoogleApiAvailability;");
115 if(CheckException(env)) return false;
116
117 jobject availabilityInstance = env->CallStaticObjectMethod(
118 availabilityClass,
119 getInstanceMethod);
120 if(CheckException(env)) return false;
121
122 jmethodID isAvailableMethod = env->GetMethodID(
123 availabilityClass,
124 "isGooglePlayServicesAvailable",
125 "(Landroid/content/Context;)I");
126 if(CheckException(env)) return false;
127
128 jint jresult = env->CallIntMethod(availabilityInstance, isAvailableMethod, context);
129 if(CheckException(env)) return false;
130
131 int result = reinterpret_cast<int>(jresult);
132
133 ALOGI("Google Play Services status : %d", result);
134
135 if(result == 0) {
136 jfieldID versionField =
137 env->GetStaticFieldID(availabilityClass, "GOOGLE_PLAY_SERVICES_VERSION_CODE", "I");
138 if(CheckException(env)) return false;
139
140 jint versionCode = env->GetStaticIntField(availabilityClass, versionField);
141 if(CheckException(env)) return false;
142
143 ALOGI("Google Play Services version : %d", versionCode);
144 }
145
146 return result == 0;
147 }
148
CheckException(JNIEnv * env)149 bool ClearcutBackend::CheckException(JNIEnv *env) {
150 if(env->ExceptionCheck()) {
151 env->ExceptionDescribe();
152 env->ExceptionClear();
153 return true;
154 }
155 return false;
156 }
157
InitWithClearcut(JNIEnv * env,jobject activity,bool anonymousLogging)158 bool ClearcutBackend::InitWithClearcut(JNIEnv* env, jobject activity, bool anonymousLogging) {
159 ALOGI("Start searching for clearcut...");
160
161 // Get Application Context
162 jclass activityClass = env->GetObjectClass(activity);
163 if (CheckException(env)) return false;
164 jmethodID getContext = env->GetMethodID(
165 activityClass,
166 "getApplicationContext",
167 "()Landroid/content/Context;");
168 if (CheckException(env)) return false;
169 jobject context = env->CallObjectMethod(activity, getContext);
170
171 //Check if Google Play Services are available
172 bool available = IsGooglePlayServiceAvailable(env, context);
173 if (!available) {
174 ALOGW("Google Play Service is not available");
175 return false;
176 }
177
178 // Searching for classes
179 jclass loggerClass = env->FindClass("com/google/android/gms/clearcut/ClearcutLogger");
180 if (CheckException(env)) return false;
181 jclass stringClass = env->FindClass("java/lang/String");
182 if (CheckException(env)) return false;
183 jclass builderClass = env->FindClass(
184 "com/google/android/gms/clearcut/ClearcutLogger$LogEventBuilder");
185 if (CheckException(env)) return false;
186
187 //Searching for all methods
188 log_method_ = env->GetMethodID(builderClass, "log", "()V");
189 if (CheckException(env)) return false;
190 new_event_ = env->GetMethodID(
191 loggerClass,
192 "newEvent",
193 "([B)Lcom/google/android/gms/clearcut/ClearcutLogger$LogEventBuilder;");
194 if (CheckException(env)) return false;
195
196 jmethodID anonymousLogger = env->GetStaticMethodID(
197 loggerClass,
198 "anonymousLogger",
199 "(Landroid/content/Context;"
200 "Ljava/lang/String;)"
201 "Lcom/google/android/gms/clearcut/ClearcutLogger;");
202 if (CheckException(env)) return false;
203
204 jmethodID loggerConstructor = env->GetMethodID(
205 loggerClass,
206 "<init>",
207 "(Landroid/content/Context;"
208 "Ljava/lang/String;"
209 "Ljava/lang/String;)"
210 "V");
211 if (CheckException(env)) return false;
212
213 //Create logger type
214 jstring ccName = env->NewStringUTF(s_clearcut_log_source);
215 if (CheckException(env)) return false;
216
217 //Create logger instance
218 jobject localClearcutLogger;
219 if (anonymousLogging) {
220 localClearcutLogger = env->CallStaticObjectMethod(loggerClass, anonymousLogger, context,
221 ccName);
222 } else {
223 localClearcutLogger = env->NewObject(loggerClass, loggerConstructor, context, ccName, NULL);
224 }
225 if (CheckException(env)) return false;
226
227 clearcut_logger_ = reinterpret_cast<jobject>(env->NewGlobalRef(localClearcutLogger));
228 if (CheckException(env)) return false;
229
230 ALOGI("Clearcut is succesfully found.");
231 return true;
232 }
233
Print(const ProtobufSerialization & evt_ser)234 void ProtoPrint::Print(const ProtobufSerialization &evt_ser) {
235 if (evt_ser.size() == 0) return;
236 auto encode_len = modp_b64_encode_len(evt_ser.size());
237 std::vector<char> dest_buf(encode_len);
238 // This fills the dest buffer with a null-terminated string. It returns the length of
239 // the string, not including the null char
240 auto n_encoded = modp_b64_encode(&dest_buf[0], reinterpret_cast<const char*>(&evt_ser[0]),
241 evt_ser.size());
242 if (n_encoded == -1 || encode_len != n_encoded+1) {
243 ALOGW("Could not b64 encode protobuf");
244 return;
245 }
246 std::string s(&dest_buf[0], n_encoded);
247 // Split the serialization into <128-byte chunks to avoid logcat line
248 // truncation.
249 constexpr size_t maxStrLen = 128;
250 int n = (s.size() + maxStrLen - 1) / maxStrLen; // Round up
251 for (int i = 0, j = 0; i < n; ++i) {
252 std::stringstream str;
253 str << "(TCL" << (i + 1) << "/" << n << ")";
254 int m = std::min(s.size() - j, maxStrLen);
255 str << s.substr(j, m);
256 j += m;
257 ALOGI("%s", str.str().c_str());
258 }
259 return;
260 }
261 }
262