1 /*
2  * Copyright (C) 2019 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 specic language governing permissions and
14  * limitations under the License.
15  */
16 
17 // Need to use LOGE_EX.
18 #define LOG_TAG "FuseDaemonJNI"
19 
20 #include <nativehelper/scoped_local_ref.h>
21 #include <nativehelper/scoped_utf_chars.h>
22 
23 #include <string>
24 #include <vector>
25 
26 #include "FuseDaemon.h"
27 #include "MediaProviderWrapper.h"
28 #include "android-base/logging.h"
29 #include "android-base/unique_fd.h"
30 
31 namespace mediaprovider {
32 namespace {
33 
34 constexpr const char* FUSE_DAEMON_CLASS_NAME = "com/android/providers/media/fuse/FuseDaemon";
35 constexpr const char* FD_ACCESS_RESULT_CLASS_NAME = "com/android/providers/media/FdAccessResult";
36 static jclass gFuseDaemonClass;
37 static jclass gFdAccessResultClass;
38 static jmethodID gFdAccessResultCtor;
39 
convert_object_array_to_string_vector(JNIEnv * env,jobjectArray java_object_array,const std::string & element_description)40 static std::vector<std::string> convert_object_array_to_string_vector(
41         JNIEnv* env, jobjectArray java_object_array, const std::string& element_description) {
42     ScopedLocalRef<jobjectArray> j_ref_object_array(env, java_object_array);
43     std::vector<std::string> utf_strings;
44 
45     const int object_array_length = env->GetArrayLength(j_ref_object_array.get());
46     for (int i = 0; i < object_array_length; i++) {
47         ScopedLocalRef<jstring> j_ref_string(
48                 env, (jstring)env->GetObjectArrayElement(j_ref_object_array.get(), i));
49         ScopedUtfChars utf_chars(env, j_ref_string.get());
50         const char* utf_string = utf_chars.c_str();
51 
52         if (utf_string) {
53             utf_strings.push_back(utf_string);
54         } else {
55             LOG(ERROR) << "Error reading " << element_description << " at index: " << i;
56         }
57     }
58 
59     return utf_strings;
60 }
61 
convert_string_vector_to_object_array(JNIEnv * env,std::vector<std::string> string_vector)62 static jobjectArray convert_string_vector_to_object_array(JNIEnv* env,
63                                                           std::vector<std::string> string_vector) {
64     jclass stringClass = env->FindClass("java/lang/String");
65     jobjectArray arr = env->NewObjectArray(string_vector.size(), stringClass, NULL);
66     for (int i = 0; i < string_vector.size(); i++) {
67         ScopedLocalRef<jstring> path(env, env->NewStringUTF(string_vector.at(i).c_str()));
68         env->SetObjectArrayElement(arr, i, path.get());
69     }
70     return arr;
71 }
72 
get_supported_transcoding_relative_paths(JNIEnv * env,jobjectArray java_supported_transcoding_relative_paths)73 static std::vector<std::string> get_supported_transcoding_relative_paths(
74         JNIEnv* env, jobjectArray java_supported_transcoding_relative_paths) {
75     return convert_object_array_to_string_vector(env, java_supported_transcoding_relative_paths,
76                                                  "supported transcoding relative path");
77 }
78 
get_supported_uncached_relative_paths(JNIEnv * env,jobjectArray java_supported_uncached_relative_paths)79 static std::vector<std::string> get_supported_uncached_relative_paths(
80         JNIEnv* env, jobjectArray java_supported_uncached_relative_paths) {
81     return convert_object_array_to_string_vector(env, java_supported_uncached_relative_paths,
82                                                  "supported uncached relative path");
83 }
84 
com_android_providers_media_FuseDaemon_new(JNIEnv * env,jobject self,jobject media_provider)85 jlong com_android_providers_media_FuseDaemon_new(JNIEnv* env, jobject self,
86                                                  jobject media_provider) {
87     LOG(DEBUG) << "Creating the FUSE daemon...";
88     return reinterpret_cast<jlong>(new fuse::FuseDaemon(env, media_provider));
89 }
90 
com_android_providers_media_FuseDaemon_start(JNIEnv * env,jobject self,jlong java_daemon,jint fd,jstring java_path,jboolean uncached_mode,jobjectArray java_supported_transcoding_relative_paths,jobjectArray java_supported_uncached_relative_paths)91 void com_android_providers_media_FuseDaemon_start(
92         JNIEnv* env, jobject self, jlong java_daemon, jint fd, jstring java_path,
93         jboolean uncached_mode, jobjectArray java_supported_transcoding_relative_paths,
94         jobjectArray java_supported_uncached_relative_paths) {
95     LOG(DEBUG) << "Starting the FUSE daemon...";
96     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
97 
98     android::base::unique_fd ufd(fd);
99 
100     ScopedUtfChars utf_chars_path(env, java_path);
101     if (!utf_chars_path.c_str()) {
102         return;
103     }
104 
105     const std::vector<std::string>& transcoding_relative_paths =
106             get_supported_transcoding_relative_paths(env,
107                     java_supported_transcoding_relative_paths);
108     const std::vector<std::string>& uncached_relative_paths =
109             get_supported_uncached_relative_paths(env, java_supported_uncached_relative_paths);
110 
111     daemon->Start(std::move(ufd), utf_chars_path.c_str(), uncached_mode, transcoding_relative_paths,
112                   uncached_relative_paths);
113 }
114 
com_android_providers_media_FuseDaemon_is_started(JNIEnv * env,jobject self,jlong java_daemon)115 bool com_android_providers_media_FuseDaemon_is_started(JNIEnv* env, jobject self,
116                                                        jlong java_daemon) {
117     LOG(DEBUG) << "Checking if FUSE daemon started...";
118     const fuse::FuseDaemon* daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
119     return daemon->IsStarted();
120 }
121 
com_android_providers_media_FuseDaemon_delete(JNIEnv * env,jobject self,jlong java_daemon)122 void com_android_providers_media_FuseDaemon_delete(JNIEnv* env, jobject self, jlong java_daemon) {
123     LOG(DEBUG) << "Destroying the FUSE daemon...";
124     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
125     delete daemon;
126 }
127 
com_android_providers_media_FuseDaemon_should_open_with_fuse(JNIEnv * env,jobject self,jlong java_daemon,jstring java_path,jboolean for_read,jint fd)128 jboolean com_android_providers_media_FuseDaemon_should_open_with_fuse(JNIEnv* env, jobject self,
129                                                                       jlong java_daemon,
130                                                                       jstring java_path,
131                                                                       jboolean for_read, jint fd) {
132     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
133     if (daemon) {
134         ScopedUtfChars utf_chars_path(env, java_path);
135         if (!utf_chars_path.c_str()) {
136             // TODO(b/145741852): Throw exception
137             return JNI_FALSE;
138         }
139 
140         return daemon->ShouldOpenWithFuse(fd, for_read, utf_chars_path.c_str());
141     }
142     // TODO(b/145741852): Throw exception
143     return JNI_FALSE;
144 }
145 
com_android_providers_media_FuseDaemon_uses_fuse_passthrough(JNIEnv * env,jobject self,jlong java_daemon)146 jboolean com_android_providers_media_FuseDaemon_uses_fuse_passthrough(JNIEnv* env, jobject self,
147                                                                       jlong java_daemon) {
148     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
149     if (daemon) {
150         return daemon->UsesFusePassthrough();
151     }
152     return JNI_FALSE;
153 }
154 
com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache(JNIEnv * env,jobject self,jlong java_daemon,jstring java_path)155 void com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache(JNIEnv* env, jobject self,
156                                                                          jlong java_daemon,
157                                                                          jstring java_path) {
158     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
159     if (daemon) {
160         ScopedUtfChars utf_chars_path(env, java_path);
161         if (!utf_chars_path.c_str()) {
162             // TODO(b/145741152): Throw exception
163             return;
164         }
165 
166         CHECK(pthread_getspecific(fuse::MediaProviderWrapper::gJniEnvKey) == nullptr);
167         daemon->InvalidateFuseDentryCache(utf_chars_path.c_str());
168     }
169     // TODO(b/145741152): Throw exception
170 }
171 
com_android_providers_media_FuseDaemon_check_fd_access(JNIEnv * env,jobject self,jlong java_daemon,jint fd,jint uid)172 jobject com_android_providers_media_FuseDaemon_check_fd_access(JNIEnv* env, jobject self,
173                                                                jlong java_daemon, jint fd,
174                                                                jint uid) {
175     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
176     const std::unique_ptr<fuse::FdAccessResult> result = daemon->CheckFdAccess(fd, uid);
177     return env->NewObject(gFdAccessResultClass, gFdAccessResultCtor,
178                           env->NewStringUTF(result->file_path.c_str()), result->should_redact);
179 }
180 
com_android_providers_media_FuseDaemon_initialize_device_id(JNIEnv * env,jobject self,jlong java_daemon,jstring java_path)181 void com_android_providers_media_FuseDaemon_initialize_device_id(JNIEnv* env, jobject self,
182                                                                  jlong java_daemon,
183                                                                  jstring java_path) {
184     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
185     ScopedUtfChars utf_chars_path(env, java_path);
186     if (!utf_chars_path.c_str()) {
187         LOG(WARNING) << "Couldn't initialise FUSE device id";
188         return;
189     }
190     daemon->InitializeDeviceId(utf_chars_path.c_str());
191 }
192 
com_android_providers_media_FuseDaemon_setup_volume_db_backup(JNIEnv * env,jobject self,jlong java_daemon)193 void com_android_providers_media_FuseDaemon_setup_volume_db_backup(JNIEnv* env, jobject self,
194                                                                    jlong java_daemon) {
195     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
196     daemon->SetupLevelDbInstances();
197 }
198 
com_android_providers_media_FuseDaemon_setup_public_volume_db_backup(JNIEnv * env,jobject self,jlong java_daemon,jstring volume_name)199 void com_android_providers_media_FuseDaemon_setup_public_volume_db_backup(JNIEnv* env, jobject self,
200                                                                    jlong java_daemon,
201                                                                    jstring volume_name) {
202     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
203     ScopedUtfChars utf_chars_volumeName(env, volume_name);
204     if (!utf_chars_volumeName.c_str()) {
205         LOG(WARNING) << "Couldn't initialise FUSE device id for " << volume_name;
206         return;
207     }
208     daemon->SetupPublicVolumeLevelDbInstance(utf_chars_volumeName.c_str());
209 }
210 
com_android_providers_media_FuseDaemon_delete_db_backup(JNIEnv * env,jobject self,jlong java_daemon,jstring java_path)211 void com_android_providers_media_FuseDaemon_delete_db_backup(JNIEnv* env, jobject self,
212                                                              jlong java_daemon, jstring java_path) {
213     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
214     ScopedUtfChars utf_chars_path(env, java_path);
215     if (!utf_chars_path.c_str()) {
216         LOG(WARNING) << "Couldn't initialise FUSE device id";
217         return;
218     }
219     daemon->DeleteFromLevelDb(utf_chars_path.c_str());
220 }
221 
com_android_providers_media_FuseDaemon_backup_volume_db_data(JNIEnv * env,jobject self,jlong java_daemon,jstring volume_name,jstring java_path,jstring value)222 void com_android_providers_media_FuseDaemon_backup_volume_db_data(JNIEnv* env, jobject self,
223                                                                   jlong java_daemon,
224                                                                   jstring volume_name,
225                                                                   jstring java_path,
226                                                                   jstring value) {
227     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
228     ScopedUtfChars utf_chars_path(env, java_path);
229     ScopedUtfChars utf_chars_value(env, value);
230     ScopedUtfChars utf_chars_volumeName(env, volume_name);
231     if (!utf_chars_path.c_str()) {
232         LOG(WARNING) << "Couldn't initialise FUSE device id";
233         return;
234     }
235     daemon->InsertInLevelDb(utf_chars_volumeName.c_str(), utf_chars_path.c_str(),
236                             utf_chars_value.c_str());
237 }
238 
com_android_providers_media_FuseDaemon_is_fuse_thread(JNIEnv * env,jclass clazz)239 bool com_android_providers_media_FuseDaemon_is_fuse_thread(JNIEnv* env, jclass clazz) {
240     return pthread_getspecific(fuse::MediaProviderWrapper::gJniEnvKey) != nullptr;
241 }
242 
com_android_providers_media_FuseDaemon_read_backed_up_file_paths(JNIEnv * env,jobject self,jlong java_daemon,jstring volumeName,jstring lastReadValue,jint limit)243 jobjectArray com_android_providers_media_FuseDaemon_read_backed_up_file_paths(
244         JNIEnv* env, jobject self, jlong java_daemon, jstring volumeName, jstring lastReadValue,
245         jint limit) {
246     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
247     ScopedUtfChars utf_chars_volumeName(env, volumeName);
248     ScopedUtfChars utf_chars_lastReadValue(env, lastReadValue);
249     if (!utf_chars_volumeName.c_str()) {
250         LOG(WARNING) << "Couldn't initialise FUSE device id";
251         return nullptr;
252     }
253     return convert_string_vector_to_object_array(
254             env, daemon->ReadFilePathsFromLevelDb(utf_chars_volumeName.c_str(),
255                                                   utf_chars_lastReadValue.c_str(), limit));
256 }
257 
com_android_providers_media_FuseDaemon_read_backed_up_data(JNIEnv * env,jobject self,jlong java_daemon,jstring java_path)258 jstring com_android_providers_media_FuseDaemon_read_backed_up_data(JNIEnv* env, jobject self,
259                                                                    jlong java_daemon,
260                                                                    jstring java_path) {
261     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
262     ScopedUtfChars utf_chars_path(env, java_path);
263     if (!utf_chars_path.c_str()) {
264         LOG(WARNING) << "Couldn't initialise FUSE device id";
265         return nullptr;
266     }
267     return env->NewStringUTF(daemon->ReadBackedUpDataFromLevelDb(utf_chars_path.c_str()).c_str());
268 }
269 
com_android_providers_media_FuseDaemon_read_ownership(JNIEnv * env,jobject self,jlong java_daemon,jstring key)270 jstring com_android_providers_media_FuseDaemon_read_ownership(JNIEnv* env, jobject self,
271                                                               jlong java_daemon, jstring key) {
272     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
273     ScopedUtfChars utf_chars_key(env, key);
274     return env->NewStringUTF(daemon->ReadOwnership(utf_chars_key.c_str()).c_str());
275 }
276 
com_android_providers_media_FuseDaemon_create_owner_id_relation(JNIEnv * env,jobject self,jlong java_daemon,jstring owner_id,jstring owner_pkg_identifier)277 void com_android_providers_media_FuseDaemon_create_owner_id_relation(JNIEnv* env, jobject self,
278                                                                      jlong java_daemon,
279                                                                      jstring owner_id,
280                                                                      jstring owner_pkg_identifier) {
281     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
282     ScopedUtfChars utf_chars_owner_id(env, owner_id);
283     ScopedUtfChars utf_chars_owner_pkg_identifier(env, owner_pkg_identifier);
284     daemon->CreateOwnerIdRelation(utf_chars_owner_id.c_str(),
285                                   utf_chars_owner_pkg_identifier.c_str());
286 }
287 
com_android_providers_media_FuseDaemon_read_owner_relations(JNIEnv * env,jobject self,jlong java_daemon)288 jobject com_android_providers_media_FuseDaemon_read_owner_relations(JNIEnv* env, jobject self,
289                                                                     jlong java_daemon) {
290     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
291     // Create a Java map object.
292     jobject map =
293             env->NewObject(env->FindClass("java/util/HashMap"),
294                            env->GetMethodID(env->FindClass("java/util/HashMap"), "<init>", "()V"));
295 
296     // Get the key-value pairs from the native method.
297     std::map<std::string, std::string> myMap = daemon->GetOwnerRelationship();
298 
299     // Iterate over the map and add the key-value pairs to the Java map.
300     for (auto it = myMap.begin(); it != myMap.end(); ++it) {
301         env->CallObjectMethod(
302                 map,
303                 env->GetMethodID(env->FindClass("java/util/HashMap"), "put",
304                                  "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
305                 env->NewStringUTF(it->first.c_str()), env->NewStringUTF(it->second.c_str()));
306     }
307 
308     // Return the Java map object.
309     return map;
310 }
311 
com_android_providers_media_FuseDaemon_remove_owner_id_relation(JNIEnv * env,jobject self,jlong java_daemon,jstring owner_id,jstring owner_pkg_identifier)312 void com_android_providers_media_FuseDaemon_remove_owner_id_relation(JNIEnv* env, jobject self,
313                                                                      jlong java_daemon,
314                                                                      jstring owner_id,
315                                                                      jstring owner_pkg_identifier) {
316     fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
317     ScopedUtfChars utf_chars_owner_id(env, owner_id);
318     ScopedUtfChars utf_chars_owner_pkg_identifier(env, owner_pkg_identifier);
319     daemon->RemoveOwnerIdRelation(utf_chars_owner_id.c_str(),
320                                   utf_chars_owner_pkg_identifier.c_str());
321 }
322 
323 const JNINativeMethod methods[] = {
324         {"native_new", "(Lcom/android/providers/media/MediaProvider;)J",
325          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_new)},
326         {"native_start", "(JILjava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;)V",
327          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_start)},
328         {"native_delete", "(J)V",
329          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_delete)},
330         {"native_should_open_with_fuse", "(JLjava/lang/String;ZI)Z",
331          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_should_open_with_fuse)},
332         {"native_uses_fuse_passthrough", "(J)Z",
333          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_uses_fuse_passthrough)},
334         {"native_is_fuse_thread", "()Z",
335          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_is_fuse_thread)},
336         {"native_is_started", "(J)Z",
337          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_is_started)},
338         {"native_invalidate_fuse_dentry_cache", "(JLjava/lang/String;)V",
339          reinterpret_cast<void*>(
340                  com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache)},
341         {"native_check_fd_access", "(JII)Lcom/android/providers/media/FdAccessResult;",
342          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_check_fd_access)},
343         {"native_initialize_device_id", "(JLjava/lang/String;)V",
344          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_initialize_device_id)},
345         {"native_setup_volume_db_backup", "(J)V",
346          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_setup_volume_db_backup)},
347         {"native_setup_public_volume_db_backup", "(JLjava/lang/String;)V",
348          reinterpret_cast<void*>(
349                  com_android_providers_media_FuseDaemon_setup_public_volume_db_backup)},
350         {"native_delete_db_backup", "(JLjava/lang/String;)V",
351          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_delete_db_backup)},
352         {"native_backup_volume_db_data",
353          "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
354          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_backup_volume_db_data)},
355         {"native_read_backed_up_file_paths",
356          "(JLjava/lang/String;Ljava/lang/String;I)[Ljava/lang/String;",
357          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_read_backed_up_file_paths)},
358         {"native_read_backed_up_data", "(JLjava/lang/String;)Ljava/lang/String;",
359          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_read_backed_up_data)},
360         {"native_read_ownership", "(JLjava/lang/String;)Ljava/lang/String;",
361          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_read_ownership)},
362         {"native_create_owner_id_relation", "(JLjava/lang/String;Ljava/lang/String;)V",
363          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_create_owner_id_relation)},
364         {"native_read_owner_relations", "(J)Ljava/util/HashMap;",
365          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_read_owner_relations)},
366         {"native_remove_owner_id_relation", "(JLjava/lang/String;Ljava/lang/String;)V",
367          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_remove_owner_id_relation)}};
368 }  // namespace
369 
register_android_providers_media_FuseDaemon(JavaVM * vm,JNIEnv * env)370 void register_android_providers_media_FuseDaemon(JavaVM* vm, JNIEnv* env) {
371     gFuseDaemonClass =
372             static_cast<jclass>(env->NewGlobalRef(env->FindClass(FUSE_DAEMON_CLASS_NAME)));
373     gFdAccessResultClass =
374             static_cast<jclass>(env->NewGlobalRef(env->FindClass(FD_ACCESS_RESULT_CLASS_NAME)));
375 
376     if (gFuseDaemonClass == nullptr) {
377         LOG(FATAL) << "Unable to find class : " << FUSE_DAEMON_CLASS_NAME;
378     }
379 
380     if (gFdAccessResultClass == nullptr) {
381         LOG(FATAL) << "Unable to find class : " << FD_ACCESS_RESULT_CLASS_NAME;
382     }
383 
384     if (env->RegisterNatives(gFuseDaemonClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
385         LOG(FATAL) << "Unable to register native methods";
386     }
387 
388     gFdAccessResultCtor = env->GetMethodID(gFdAccessResultClass, "<init>", "(Ljava/lang/String;Z)V");
389     if (gFdAccessResultCtor == nullptr) {
390         LOG(FATAL) << "Unable to find ctor for FdAccessResult";
391     }
392 
393     fuse::MediaProviderWrapper::OneTimeInit(vm);
394 }
395 }  // namespace mediaprovider
396