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