1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.app.Application; 8 import android.content.Context; 9 import android.content.ContextWrapper; 10 import android.content.SharedPreferences; 11 import android.content.res.AssetManager; 12 import android.os.Process; 13 import android.preference.PreferenceManager; 14 15 import org.chromium.base.annotations.JNINamespace; 16 import org.chromium.base.annotations.MainDex; 17 18 /** 19 * This class provides Android application context related utility methods. 20 */ 21 @JNINamespace("base::android") 22 public class ContextUtils { 23 private static final String TAG = "ContextUtils"; 24 private static Context sApplicationContext; 25 // TODO(agrieve): Remove sProcessName caching when we stop supporting JB. 26 private static String sProcessName; 27 28 /** 29 * Initialization-on-demand holder. This exists for thread-safe lazy initialization. 30 */ 31 private static class Holder { 32 // Not final for tests. 33 private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences(); 34 } 35 36 /** 37 * Get the Android application context. 38 * 39 * Under normal circumstances there is only one application context in a process, so it's safe 40 * to treat this as a global. In WebView it's possible for more than one app using WebView to be 41 * running in a single process, but this mechanism is rarely used and this is not the only 42 * problem in that scenario, so we don't currently forbid using it as a global. 43 * 44 * Do not downcast the context returned by this method to Application (or any subclass). It may 45 * not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you 46 * may make is that it is a Context whose lifetime is the same as the lifetime of the process. 47 */ getApplicationContext()48 public static Context getApplicationContext() { 49 return sApplicationContext; 50 } 51 52 /** 53 * Initializes the java application context. 54 * 55 * This should be called exactly once early on during startup, before native is loaded and 56 * before any other clients make use of the application context through this class. 57 * 58 * @param appContext The application context. 59 */ 60 @MainDex // TODO(agrieve): Could add to whole class if not for ApplicationStatus.initialize(). initApplicationContext(Context appContext)61 public static void initApplicationContext(Context appContext) { 62 // Conceding that occasionally in tests, native is loaded before the browser process is 63 // started, in which case the browser process re-sets the application context. 64 if (sApplicationContext != null && sApplicationContext != appContext) { 65 throw new RuntimeException("Attempting to set multiple global application contexts."); 66 } 67 initJavaSideApplicationContext(appContext); 68 } 69 70 /** 71 * Only called by the static holder class and tests. 72 * 73 * @return The application-wide shared preferences. 74 */ fetchAppSharedPreferences()75 private static SharedPreferences fetchAppSharedPreferences() { 76 return PreferenceManager.getDefaultSharedPreferences(sApplicationContext); 77 } 78 79 /** 80 * This is used to ensure that we always use the application context to fetch the default shared 81 * preferences. This avoids needless I/O for android N and above. It also makes it clear that 82 * the app-wide shared preference is desired, rather than the potentially context-specific one. 83 * 84 * @return application-wide shared preferences. 85 */ getAppSharedPreferences()86 public static SharedPreferences getAppSharedPreferences() { 87 return Holder.sSharedPreferences; 88 } 89 90 /** 91 * Occasionally tests cannot ensure the application context doesn't change between tests (junit) 92 * and sometimes specific tests has its own special needs, initApplicationContext should be used 93 * as much as possible, but this method can be used to override it. 94 * 95 * @param appContext The new application context. 96 */ 97 @VisibleForTesting initApplicationContextForTests(Context appContext)98 public static void initApplicationContextForTests(Context appContext) { 99 // ApplicationStatus.initialize should be called to setup activity tracking for tests 100 // that use Robolectric and set the application context manually. Instead of changing all 101 // tests that do so, the call was put here instead. 102 // TODO(mheikal): Require param to be of type Application 103 // Disabled on libchrome 104 // if (appContext instanceof Application) { 105 // ApplicationStatus.initialize((Application) appContext); 106 // } 107 initJavaSideApplicationContext(appContext); 108 Holder.sSharedPreferences = fetchAppSharedPreferences(); 109 } 110 initJavaSideApplicationContext(Context appContext)111 private static void initJavaSideApplicationContext(Context appContext) { 112 if (appContext == null) { 113 throw new RuntimeException("Global application context cannot be set to null."); 114 } 115 sApplicationContext = appContext; 116 } 117 118 /** 119 * In most cases, {@link Context#getAssets()} can be used directly. Modified resources are 120 * used downstream and are set up on application startup, and this method provides access to 121 * regular assets before that initialization is complete. 122 * 123 * This method should ONLY be used for accessing files within the assets folder. 124 * 125 * @return Application assets. 126 */ getApplicationAssets()127 public static AssetManager getApplicationAssets() { 128 Context context = getApplicationContext(); 129 while (context instanceof ContextWrapper) { 130 context = ((ContextWrapper) context).getBaseContext(); 131 } 132 return context.getAssets(); 133 } 134 135 /** 136 * @return Whether the process is isolated. 137 */ isIsolatedProcess()138 public static boolean isIsolatedProcess() { 139 try { 140 return (Boolean) Process.class.getMethod("isIsolated").invoke(null); 141 } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions. 142 // If fallback logic is ever needed, refer to: 143 // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1 144 throw new RuntimeException(e); 145 } 146 } 147 148 /** @return The name of the current process. E.g. "org.chromium.chrome:privileged_process0". */ getProcessName()149 public static String getProcessName() { 150 // Once we drop support JB, this method can be simplified to not cache sProcessName and call 151 // ActivityThread.currentProcessName(). 152 if (sProcessName != null) { 153 return sProcessName; 154 } 155 try { 156 // An even more convenient ActivityThread.currentProcessName() exists, but was not added 157 // until JB MR2. 158 Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread"); 159 Object activityThread = 160 activityThreadClazz.getMethod("currentActivityThread").invoke(null); 161 // Before JB MR2, currentActivityThread() returns null when called on a non-UI thread. 162 // Cache the name to allow other threads to access it. 163 sProcessName = 164 (String) activityThreadClazz.getMethod("getProcessName").invoke(activityThread); 165 return sProcessName; 166 } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions. 167 // If fallback logic is ever needed, refer to: 168 // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1 169 throw new RuntimeException(e); 170 } 171 } 172 isMainProcess()173 public static boolean isMainProcess() { 174 return !getProcessName().contains(":"); 175 } 176 } 177