From: Calin Juravle Date: Wed, 24 Feb 2016 10:13:09 +0000 (+0000) Subject: Record foreign dex files loaded by the app in the profile X-Git-Tag: android-x86-7.1-r1~370^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=86a9ebe4197e963249ffbbaa1830da97ed642fa5;p=android-x86%2Fart.git Record foreign dex files loaded by the app in the profile A foreign dex file is a file which is not owned by the app (it's not part of its code paths or its private data directory). When such a dex file is loaded by the app, the runtime will record a marker in a dedicated profile folder (foreing_dex_profile_path). The marker is just a file named after the canonical location of the dex file where '/' is replaced by '@'. The markers will be used by the system server system server to decide if the apk should be fully or profile guide compiled. Bug: 27334750 Bug: 26080105 Change-Id: If4fa8208be4e2f6f0b748b8a5417c4ae9c2d5df6 --- diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index bdc7ee242..e3b453ced 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -193,9 +193,11 @@ void Jit::DeleteThreadPool() { } void Jit::StartProfileSaver(const std::string& filename, - const std::vector& code_paths) { + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_dir) { if (save_profiling_info_) { - ProfileSaver::Start(filename, code_cache_.get(), code_paths); + ProfileSaver::Start(filename, code_cache_.get(), code_paths, foreign_dex_profile_path, app_dir); } } diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 109ca3dbd..570f68359 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -70,7 +70,17 @@ class Jit { return instrumentation_cache_.get(); } - void StartProfileSaver(const std::string& filename, const std::vector& code_paths); + // Starts the profile saver if the config options allow profile recording. + // The profile will be stored in the specified `filename` and will contain + // information collected from the given `code_paths` (a set of dex locations). + // The `foreign_dex_profile_path` is the path where the saver will put the + // profile markers for loaded dex files which are not owned by the application. + // The `app_dir` is the application directory and is used to decide which + // dex files belong to the application. + void StartProfileSaver(const std::string& filename, + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_dir); void StopProfileSaver(); void DumpForSigQuit(std::ostream& os) { diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index b1a5a4b24..4659cbcf1 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -16,6 +16,10 @@ #include "profile_saver.h" +#include +#include +#include + #include "art_method-inl.h" #include "scoped_thread_state_change.h" #include "oat_file_manager.h" @@ -41,13 +45,30 @@ pthread_t ProfileSaver::profiler_pthread_ = 0U; ProfileSaver::ProfileSaver(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, - const std::vector& code_paths) + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir) : jit_code_cache_(jit_code_cache), + foreign_dex_profile_path_(foreign_dex_profile_path), code_cache_last_update_time_ns_(0), shutting_down_(false), wait_lock_("ProfileSaver wait lock"), period_condition_("ProfileSaver period condition", wait_lock_) { AddTrackedLocations(output_filename, code_paths); + app_data_dir_ = ""; + if (!app_data_dir.empty()) { + // The application directory is used to determine which dex files are owned by app. + // Since it could be a symlink (e.g. /data/data instead of /data/user/0), and we + // don't have control over how the dex files are actually loaded (symlink or canonical path), + // store it's canonical form to be sure we use the same base when comparing. + UniqueCPtr app_data_dir_real_path(realpath(app_data_dir.c_str(), nullptr)); + if (app_data_dir_real_path != nullptr) { + app_data_dir_.assign(app_data_dir_real_path.get()); + } else { + LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir_ + << ". The app dir will not be used to determine which dex files belong to the app"; + } + } } void ProfileSaver::Run() { @@ -146,7 +167,9 @@ void* ProfileSaver::RunProfileSaverThread(void* arg) { void ProfileSaver::Start(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, - const std::vector& code_paths) { + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir) { DCHECK(Runtime::Current()->UseJit()); DCHECK(!output_filename.empty()); DCHECK(jit_code_cache != nullptr); @@ -165,7 +188,11 @@ void ProfileSaver::Start(const std::string& output_filename, VLOG(profiler) << "Starting profile saver using output file: " << output_filename << ". Tracking: " << Join(code_paths, ':'); - instance_ = new ProfileSaver(output_filename, jit_code_cache, code_paths); + instance_ = new ProfileSaver(output_filename, + jit_code_cache, + code_paths, + foreign_dex_profile_path, + app_data_dir); // Create a new thread which does the saving. CHECK_PTHREAD_CALL( @@ -232,4 +259,83 @@ void ProfileSaver::AddTrackedLocations(const std::string& output_filename, } } +void ProfileSaver::NotifyDexUse(const std::string& dex_location) { + std::set app_code_paths; + std::string foreign_dex_profile_path; + std::string app_data_dir; + { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + DCHECK(instance_ != nullptr); + // Make a copy so that we don't hold the lock while doing I/O. + for (const auto& it : instance_->tracked_dex_base_locations_) { + app_code_paths.insert(it.second.begin(), it.second.end()); + } + foreign_dex_profile_path = instance_->foreign_dex_profile_path_; + app_data_dir = instance_->app_data_dir_; + } + + MaybeRecordDexUseInternal(dex_location, + app_code_paths, + foreign_dex_profile_path, + app_data_dir); +} + +void ProfileSaver::MaybeRecordDexUseInternal( + const std::string& dex_location, + const std::set& app_code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir) { + if (foreign_dex_profile_path.empty()) { + LOG(WARNING) << "Asked to record foreign dex use without a valid profile path "; + return; + } + + UniqueCPtr dex_location_real_path(realpath(dex_location.c_str(), nullptr)); + std::string dex_location_real_path_str(dex_location_real_path.get()); + + if (dex_location_real_path_str.compare(0, app_data_dir.length(), app_data_dir) == 0) { + // The dex location is under the application folder. Nothing to record. + return; + } + + if (app_code_paths.find(dex_location) != app_code_paths.end()) { + // The dex location belongs to the application code paths. Nothing to record. + return; + } + // Do another round of checks with the real paths. + // Note that we could cache all the real locations in the saver (since it's an expensive + // operation). However we expect that app_code_paths is small (usually 1 element), and + // NotifyDexUse is called just a few times in the app lifetime. So we make the compromise + // to save some bytes of memory usage. + for (const auto& app_code_location : app_code_paths) { + UniqueCPtr real_app_code_location(realpath(app_code_location.c_str(), nullptr)); + std::string real_app_code_location_str(real_app_code_location.get()); + if (real_app_code_location_str == dex_location_real_path_str) { + // The dex location belongs to the application code paths. Nothing to record. + return; + } + } + + // For foreign dex files we record a flag on disk. PackageManager will (potentially) take this + // into account when deciding how to optimize the loaded dex file. + // The expected flag name is the canonical path of the apk where '/' is substituted to '@'. + // (it needs to be kept in sync with + // frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java) + std::replace(dex_location_real_path_str.begin(), dex_location_real_path_str.end(), '/', '@'); + std::string flag_path = foreign_dex_profile_path + "/" + dex_location_real_path_str; + // No need to give any sort of access to flag_path. The system has enough permissions + // to test for its existence. + int fd = TEMP_FAILURE_RETRY(open(flag_path.c_str(), O_CREAT | O_EXCL, 0)); + if (fd != -1) { + if (close(fd) != 0) { + PLOG(WARNING) << "Could not close file after flagging foreign dex use " << flag_path; + } + } else { + if (errno != EEXIST) { + // Another app could have already created the file. + PLOG(WARNING) << "Could not create foreign dex use mark " << flag_path; + } + } +} + } // namespace art diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h index 3342790e4..4d7118850 100644 --- a/runtime/jit/profile_saver.h +++ b/runtime/jit/profile_saver.h @@ -30,7 +30,9 @@ class ProfileSaver { // If the saver is already running it adds (output_filename, code_paths) to its tracked locations. static void Start(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, - const std::vector& code_paths) + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir) REQUIRES(!Locks::profiler_lock_, !wait_lock_); // Stops the profile saver thread. @@ -42,10 +44,14 @@ class ProfileSaver { // Returns true if the profile saver is started. static bool IsStarted() REQUIRES(!Locks::profiler_lock_); + static void NotifyDexUse(const std::string& dex_location); + private: ProfileSaver(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, - const std::vector& code_paths); + const std::vector& code_paths, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir); // NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock. static void* RunProfileSaverThread(void* arg) @@ -64,6 +70,12 @@ class ProfileSaver { const std::vector& code_paths) REQUIRES(Locks::profiler_lock_); + static void MaybeRecordDexUseInternal( + const std::string& dex_location, + const std::set& tracked_locations, + const std::string& foreign_dex_profile_path, + const std::string& app_data_dir); + // The only instance of the saver. static ProfileSaver* instance_ GUARDED_BY(Locks::profiler_lock_); // Profile saver thread. @@ -72,6 +84,8 @@ class ProfileSaver { jit::JitCodeCache* jit_code_cache_; SafeMap> tracked_dex_base_locations_ GUARDED_BY(Locks::profiler_lock_); + std::string foreign_dex_profile_path_; + std::string app_data_dir_; uint64_t code_cache_last_update_time_ns_; bool shutting_down_ GUARDED_BY(Locks::profiler_lock_); diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc index da4a891ff..f6b2f2151 100644 --- a/runtime/native/dalvik_system_VMRuntime.cc +++ b/runtime/native/dalvik_system_VMRuntime.cc @@ -566,8 +566,9 @@ static void VMRuntime_preloadDexCaches(JNIEnv* env, jobject) { static void VMRuntime_registerAppInfo(JNIEnv* env, jclass clazz ATTRIBUTE_UNUSED, jstring profile_file, - jstring app_dir ATTRIBUTE_UNUSED, // TODO: remove argument - jobjectArray code_paths) { + jstring app_dir, + jobjectArray code_paths, + jstring foreign_dex_profile_path) { std::vector code_paths_vec; int code_paths_length = env->GetArrayLength(code_paths); for (int i = 0; i < code_paths_length; i++) { @@ -581,7 +582,22 @@ static void VMRuntime_registerAppInfo(JNIEnv* env, std::string profile_file_str(raw_profile_file); env->ReleaseStringUTFChars(profile_file, raw_profile_file); - Runtime::Current()->RegisterAppInfo(code_paths_vec, profile_file_str); + std::string foreign_dex_profile_path_str = ""; + if (foreign_dex_profile_path != nullptr) { + const char* raw_foreign_dex_profile_path = + env->GetStringUTFChars(foreign_dex_profile_path, nullptr); + foreign_dex_profile_path_str.assign(raw_foreign_dex_profile_path); + env->ReleaseStringUTFChars(foreign_dex_profile_path, raw_foreign_dex_profile_path); + } + + const char* raw_app_dir = env->GetStringUTFChars(app_dir, nullptr); + std::string app_dir_str(raw_app_dir); + env->ReleaseStringUTFChars(app_dir, raw_app_dir); + + Runtime::Current()->RegisterAppInfo(code_paths_vec, + profile_file_str, + foreign_dex_profile_path_str, + app_dir_str); } static jboolean VMRuntime_isBootClassPathOnDisk(JNIEnv* env, jclass, jstring java_instruction_set) { @@ -638,7 +654,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "!()Z"), NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"), NATIVE_METHOD(VMRuntime, registerAppInfo, - "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"), + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V"), NATIVE_METHOD(VMRuntime, isBootClassPathOnDisk, "(Ljava/lang/String;)Z"), NATIVE_METHOD(VMRuntime, getCurrentInstructionSet, "()Ljava/lang/String;"), }; diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 18cf81aa7..dff4a77f6 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -439,6 +439,10 @@ std::vector> OatFileManager::OpenDexFilesFromOat( + std::string(dex_location)); } } + + // TODO(calin): Consider optimizing this knowing that is useless to record the + // use of fully compiled apks. + Runtime::Current()->NotifyDexLoaded(dex_location); return dex_files; } diff --git a/runtime/runtime.cc b/runtime/runtime.cc index eb5455a4c..fe25cdcc2 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -120,6 +120,7 @@ #include "os.h" #include "parsed_options.h" #include "profiler.h" +#include "jit/profile_saver.h" #include "quick/quick_method_frame_info.h" #include "reflection.h" #include "runtime_options.h" @@ -1713,7 +1714,9 @@ void Runtime::SetCalleeSaveMethod(ArtMethod* method, CalleeSaveType type) { } void Runtime::RegisterAppInfo(const std::vector& code_paths, - const std::string& profile_output_filename) { + const std::string& profile_output_filename, + const std::string& foreign_dex_profile_path, + const std::string& app_dir) { if (jit_.get() == nullptr) { // We are not JITing. Nothing to do. return; @@ -1736,7 +1739,18 @@ void Runtime::RegisterAppInfo(const std::vector& code_paths, } profile_output_filename_ = profile_output_filename; - jit_->StartProfileSaver(profile_output_filename, code_paths); + jit_->StartProfileSaver(profile_output_filename, + code_paths, + foreign_dex_profile_path, + app_dir); +} + +void Runtime::NotifyDexLoaded(const std::string& dex_location) { + VLOG(profiler) << "Notify dex loaded: " << dex_location; + // We know that if the ProfileSaver is started then we can record profile information. + if (ProfileSaver::IsStarted()) { + ProfileSaver::NotifyDexUse(dex_location); + } } // Transaction support. diff --git a/runtime/runtime.h b/runtime/runtime.h index 8aac4ce9b..ba75a8b8f 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -467,7 +467,10 @@ class Runtime { } void RegisterAppInfo(const std::vector& code_paths, - const std::string& profile_output_filename); + const std::string& profile_output_filename, + const std::string& foreign_dex_profile_path, + const std::string& app_dir); + void NotifyDexLoaded(const std::string& dex_location); // Transaction support. bool IsActiveTransaction() const { diff --git a/test/577-profile-foreign-dex/expected.txt b/test/577-profile-foreign-dex/expected.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/577-profile-foreign-dex/info.txt b/test/577-profile-foreign-dex/info.txt new file mode 100644 index 000000000..090db3fdc --- /dev/null +++ b/test/577-profile-foreign-dex/info.txt @@ -0,0 +1 @@ +Check that we record the use of foreign dex files when profiles are enabled. diff --git a/test/577-profile-foreign-dex/run b/test/577-profile-foreign-dex/run new file mode 100644 index 000000000..ad57d14c6 --- /dev/null +++ b/test/577-profile-foreign-dex/run @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +exec ${RUN} \ + --runtime-option -Xjitsaveprofilinginfo \ + --runtime-option -Xusejit:true \ + "${@}" diff --git a/test/577-profile-foreign-dex/src-ex/OtherDex.java b/test/577-profile-foreign-dex/src-ex/OtherDex.java new file mode 100644 index 000000000..cba73b309 --- /dev/null +++ b/test/577-profile-foreign-dex/src-ex/OtherDex.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +public class OtherDex { +} diff --git a/test/577-profile-foreign-dex/src/Main.java b/test/577-profile-foreign-dex/src/Main.java new file mode 100644 index 000000000..0cd85b58e --- /dev/null +++ b/test/577-profile-foreign-dex/src/Main.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.util.HashMap; + +public class Main { + + private static final String PROFILE_NAME = "primary.prof"; + private static final String APP_DIR_PREFIX = "app_dir_"; + private static final String FOREIGN_DEX_PROFILE_DIR = "foreign-dex"; + private static final String TEMP_FILE_NAME_PREFIX = "dummy"; + private static final String TEMP_FILE_NAME_SUFFIX = "-file"; + + public static void main(String[] args) throws Exception { + File tmpFile = null; + File appDir = null; + File profileFile = null; + File foreignDexProfileDir = null; + + try { + // Create the necessary files layout. + tmpFile = createTempFile(); + appDir = new File(tmpFile.getParent(), APP_DIR_PREFIX + tmpFile.getName()); + appDir.mkdir(); + foreignDexProfileDir = new File(tmpFile.getParent(), FOREIGN_DEX_PROFILE_DIR); + foreignDexProfileDir.mkdir(); + profileFile = createTempFile(); + + String codePath = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex.jar"; + + // Register the app with the runtime + VMRuntime.registerAppInfo(profileFile.getPath(), appDir.getPath(), + new String[] { codePath }, foreignDexProfileDir.getPath()); + + testMarkerForForeignDex(foreignDexProfileDir); + testMarkerForCodePath(foreignDexProfileDir); + testMarkerForApplicationDexFile(foreignDexProfileDir, appDir); + } finally { + if (tmpFile != null) { + tmpFile.delete(); + } + if (profileFile != null) { + profileFile.delete(); + } + if (foreignDexProfileDir != null) { + foreignDexProfileDir.delete(); + } + if (appDir != null) { + appDir.delete(); + } + } + } + + // Verify we actually create a marker on disk for foreign dex files. + private static void testMarkerForForeignDex(File foreignDexProfileDir) throws Exception { + String foreignDex = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex-ex.jar"; + loadDexFile(foreignDex); + checkMarker(foreignDexProfileDir, foreignDex, /* exists */ true); + } + + // Verify we do not create a marker on disk for dex files path of the code path. + private static void testMarkerForCodePath(File foreignDexProfileDir) throws Exception { + String codePath = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex.jar"; + loadDexFile(codePath); + checkMarker(foreignDexProfileDir, codePath, /* exists */ false); + } + + private static void testMarkerForApplicationDexFile(File foreignDexProfileDir, File appDir) + throws Exception { + // Copy the -ex jar to the application directory and load it from there. + // This will record duplicate class conflicts but we don't care for this use case. + File foreignDex = new File(System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex-ex.jar"); + File appDex = new File(appDir, "appDex.jar"); + try { + copyFile(foreignDex, appDex); + + loadDexFile(appDex.getAbsolutePath()); + checkMarker(foreignDexProfileDir, appDex.getAbsolutePath(), /* exists */ false); + } finally { + if (appDex != null) { + appDex.delete(); + } + } + } + + private static void checkMarker(File foreignDexProfileDir, String dexFile, boolean exists) { + File marker = new File(foreignDexProfileDir, dexFile.replace('/', '@')); + boolean result_ok = exists ? marker.exists() : !marker.exists(); + if (!result_ok) { + throw new RuntimeException("Marker test failed for:" + marker.getPath()); + } + } + + private static void loadDexFile(String dexFile) throws Exception { + Class pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new RuntimeException("Couldn't find path class loader class"); + } + Constructor constructor = + pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class); + constructor.newInstance( + dexFile, ClassLoader.getSystemClassLoader()); + } + + private static class VMRuntime { + private static final Method registerAppInfoMethod; + static { + try { + Class c = Class.forName("dalvik.system.VMRuntime"); + registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo", + String.class, String.class, String[].class, String.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void registerAppInfo(String pkgName, String appDir, + String[] codePath, String foreignDexProfileDir) throws Exception { + registerAppInfoMethod.invoke(null, pkgName, appDir, codePath, foreignDexProfileDir); + } + } + + private static void copyFile(File fromFile, File toFile) throws Exception { + FileInputStream in = new FileInputStream(fromFile); + FileOutputStream out = new FileOutputStream(toFile); + try { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) >= 0) { + out.write(buffer, 0, bytesRead); + } + } finally { + out.flush(); + try { + out.getFD().sync(); + } catch (IOException e) { + } + out.close(); + in.close(); + } + } + + private static File createTempFile() throws Exception { + try { + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } catch (IOException e) { + System.setProperty("java.io.tmpdir", "/data/local/tmp"); + try { + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } catch (IOException e2) { + System.setProperty("java.io.tmpdir", "/sdcard"); + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } + } + } +}