MainApp.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author masensio
  5. * @author David A. Velasco
  6. * @author Chris Narkiewicz
  7. * Copyright (C) 2015 ownCloud Inc.
  8. * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.owncloud.android;
  23. import android.Manifest;
  24. import android.annotation.SuppressLint;
  25. import android.app.Activity;
  26. import android.app.ActivityManager;
  27. import android.app.Application;
  28. import android.app.NotificationChannel;
  29. import android.app.NotificationManager;
  30. import android.content.ContentResolver;
  31. import android.content.Context;
  32. import android.content.Intent;
  33. import android.content.pm.PackageInfo;
  34. import android.content.pm.PackageManager;
  35. import android.os.Build;
  36. import android.os.Bundle;
  37. import android.os.Environment;
  38. import android.os.StrictMode;
  39. import android.text.TextUtils;
  40. import android.view.WindowManager;
  41. import com.evernote.android.job.JobManager;
  42. import com.nextcloud.client.account.User;
  43. import com.nextcloud.client.account.UserAccountManager;
  44. import com.nextcloud.client.appinfo.AppInfo;
  45. import com.nextcloud.client.core.Clock;
  46. import com.nextcloud.client.device.PowerManagementService;
  47. import com.nextcloud.client.di.ActivityInjector;
  48. import com.nextcloud.client.di.DaggerAppComponent;
  49. import com.nextcloud.client.errorhandling.ExceptionHandler;
  50. import com.nextcloud.client.jobs.BackgroundJobManager;
  51. import com.nextcloud.client.logger.LegacyLoggerAdapter;
  52. import com.nextcloud.client.logger.Logger;
  53. import com.nextcloud.client.migrations.MigrationsManager;
  54. import com.nextcloud.client.network.ConnectivityService;
  55. import com.nextcloud.client.onboarding.OnboardingService;
  56. import com.nextcloud.client.preferences.AppPreferences;
  57. import com.nextcloud.client.preferences.AppPreferencesImpl;
  58. import com.nextcloud.client.preferences.DarkMode;
  59. import com.owncloud.android.authentication.PassCodeManager;
  60. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  61. import com.owncloud.android.datamodel.MediaFolder;
  62. import com.owncloud.android.datamodel.MediaFolderType;
  63. import com.owncloud.android.datamodel.MediaProvider;
  64. import com.owncloud.android.datamodel.SyncedFolder;
  65. import com.owncloud.android.datamodel.SyncedFolderProvider;
  66. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  67. import com.owncloud.android.datamodel.UploadsStorageManager;
  68. import com.owncloud.android.datastorage.DataStorageProvider;
  69. import com.owncloud.android.datastorage.StoragePoint;
  70. import com.owncloud.android.jobs.NCJobCreator;
  71. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  72. import com.owncloud.android.lib.common.utils.Log_OC;
  73. import com.owncloud.android.lib.resources.status.OwnCloudVersion;
  74. import com.owncloud.android.ui.activity.SyncedFoldersActivity;
  75. import com.owncloud.android.ui.notifications.NotificationUtils;
  76. import com.owncloud.android.utils.DisplayUtils;
  77. import com.owncloud.android.utils.FilesSyncHelper;
  78. import com.owncloud.android.utils.PermissionUtil;
  79. import com.owncloud.android.utils.ReceiversHelper;
  80. import com.owncloud.android.utils.SecurityUtils;
  81. import org.conscrypt.Conscrypt;
  82. import org.greenrobot.eventbus.EventBus;
  83. import java.lang.reflect.Method;
  84. import java.security.NoSuchAlgorithmException;
  85. import java.security.Security;
  86. import java.util.ArrayList;
  87. import java.util.Arrays;
  88. import java.util.HashMap;
  89. import java.util.List;
  90. import java.util.Locale;
  91. import java.util.Map;
  92. import javax.inject.Inject;
  93. import javax.net.ssl.SSLContext;
  94. import javax.net.ssl.SSLEngine;
  95. import androidx.annotation.RequiresApi;
  96. import androidx.annotation.StringRes;
  97. import androidx.appcompat.app.AlertDialog;
  98. import androidx.appcompat.app.AppCompatDelegate;
  99. import androidx.core.util.Pair;
  100. import androidx.multidex.MultiDexApplication;
  101. import dagger.android.AndroidInjector;
  102. import dagger.android.DispatchingAndroidInjector;
  103. import dagger.android.HasAndroidInjector;
  104. import de.cotech.hw.SecurityKeyManager;
  105. import de.cotech.hw.SecurityKeyManagerConfig;
  106. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  107. import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP;
  108. /**
  109. * Main Application of the project
  110. *
  111. * Contains methods to build the "static" strings. These strings were before constants in different classes
  112. */
  113. public class MainApp extends MultiDexApplication implements HasAndroidInjector {
  114. public static final OwnCloudVersion OUTDATED_SERVER_VERSION = OwnCloudVersion.nextcloud_15;
  115. public static final OwnCloudVersion MINIMUM_SUPPORTED_SERVER_VERSION = OwnCloudVersion.nextcloud_13;
  116. private static final String TAG = MainApp.class.getSimpleName();
  117. public static final String DOT = ".";
  118. private static Context mContext;
  119. private static String storagePath;
  120. private static boolean mOnlyOnDevice;
  121. @Inject
  122. protected AppPreferences preferences;
  123. @Inject
  124. protected DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
  125. @Inject
  126. protected UserAccountManager accountManager;
  127. @Inject
  128. protected UploadsStorageManager uploadsStorageManager;
  129. @Inject
  130. protected OnboardingService onboarding;
  131. @Inject
  132. ConnectivityService connectivityService;
  133. @Inject PowerManagementService powerManagementService;
  134. @Inject
  135. Logger logger;
  136. @Inject
  137. AppInfo appInfo;
  138. @Inject
  139. BackgroundJobManager backgroundJobManager;
  140. @Inject
  141. Clock clock;
  142. @Inject
  143. EventBus eventBus;
  144. @Inject
  145. MigrationsManager migrationsManager;
  146. private PassCodeManager passCodeManager;
  147. @SuppressWarnings("unused")
  148. private boolean mBound;
  149. /**
  150. * Temporary hack
  151. */
  152. private static void initGlobalContext(Context context) {
  153. mContext = context;
  154. }
  155. /**
  156. * Temporary getter replacing Dagger DI
  157. * TODO: remove when cleaning DI in NContentObserverJob
  158. */
  159. public AppPreferences getPreferences() {
  160. return preferences;
  161. }
  162. /**
  163. * Temporary getter replacing Dagger DI
  164. * TODO: remove when cleaning DI in NContentObserverJob
  165. */
  166. public PowerManagementService getPowerManagementService() {
  167. return powerManagementService;
  168. }
  169. private String getAppProcessName() {
  170. String processName = "";
  171. if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
  172. ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
  173. final int ownPid = android.os.Process.myPid();
  174. final List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
  175. if (processes != null) {
  176. for (ActivityManager.RunningAppProcessInfo info : processes) {
  177. if (info.pid == ownPid) {
  178. processName = info.processName;
  179. break;
  180. }
  181. }
  182. }
  183. } else {
  184. processName = Application.getProcessName();
  185. }
  186. return processName;
  187. }
  188. @Override
  189. protected void attachBaseContext(Context base) {
  190. super.attachBaseContext(base);
  191. initGlobalContext(this);
  192. DaggerAppComponent.builder()
  193. .application(this)
  194. .build()
  195. .inject(this);
  196. // we don't want to handle crashes occurring inside crash reporter activity/process;
  197. // let the platform deal with those
  198. final boolean isCrashReportingProcess = getAppProcessName().endsWith(":crash");
  199. final boolean useExceptionHandler = !appInfo.isDebugBuild();
  200. if (!isCrashReportingProcess && useExceptionHandler) {
  201. Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
  202. final ExceptionHandler crashReporter = new ExceptionHandler(this,
  203. defaultPlatformHandler);
  204. Thread.setDefaultUncaughtExceptionHandler(crashReporter);
  205. }
  206. }
  207. @SuppressFBWarnings("ST")
  208. @Override
  209. public void onCreate() {
  210. setAppTheme(preferences.getDarkThemeMode());
  211. super.onCreate();
  212. insertConscrypt();
  213. initSecurityKeyManager();
  214. registerActivityLifecycleCallbacks(new ActivityInjector());
  215. int startedMigrationsCount = migrationsManager.startMigration();
  216. logger.i(TAG, String.format(Locale.US, "Started %d migrations", startedMigrationsCount));
  217. JobManager.create(this).addJobCreator(
  218. new NCJobCreator(
  219. accountManager,
  220. uploadsStorageManager,
  221. clock,
  222. eventBus,
  223. backgroundJobManager
  224. )
  225. );
  226. new SecurityUtils();
  227. DisplayUtils.useCompatVectorIfNeeded();
  228. fixStoragePath();
  229. passCodeManager = new PassCodeManager(preferences);
  230. MainApp.storagePath = preferences.getStoragePath(getApplicationContext().getFilesDir().getAbsolutePath());
  231. OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
  232. // initialise thumbnails cache on background thread
  233. new ThumbnailsCacheManager.InitDiskCacheTask().execute();
  234. if (BuildConfig.DEBUG || getApplicationContext().getResources().getBoolean(R.bool.logger_enabled)) {
  235. // use app writable dir, no permissions needed
  236. Log_OC.setLoggerImplementation(new LegacyLoggerAdapter(logger));
  237. Log_OC.d("Debug", "start logging");
  238. }
  239. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  240. try {
  241. Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
  242. m.invoke(null);
  243. } catch (Exception e) {
  244. Log_OC.d("Debug", "Failed to disable uri exposure");
  245. }
  246. }
  247. initSyncOperations(preferences,
  248. uploadsStorageManager,
  249. accountManager,
  250. connectivityService,
  251. powerManagementService,
  252. backgroundJobManager,
  253. clock);
  254. initContactsBackup(accountManager, backgroundJobManager);
  255. notificationChannels();
  256. backgroundJobManager.scheduleMediaFoldersDetectionJob();
  257. backgroundJobManager.startMediaFoldersDetectionJob();
  258. registerGlobalPassCodeProtection();
  259. }
  260. private void registerGlobalPassCodeProtection() {
  261. registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
  262. @Override
  263. public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
  264. Log_OC.d(activity.getClass().getSimpleName(), "onCreate(Bundle) starting");
  265. onboarding.launchActivityIfNeeded(activity);
  266. }
  267. @Override
  268. public void onActivityStarted(Activity activity) {
  269. Log_OC.d(activity.getClass().getSimpleName(), "onStart() starting");
  270. }
  271. @Override
  272. public void onActivityResumed(Activity activity) {
  273. Log_OC.d(activity.getClass().getSimpleName(), "onResume() starting");
  274. passCodeManager.onActivityStarted(activity);
  275. }
  276. @Override
  277. public void onActivityPaused(Activity activity) {
  278. Log_OC.d(activity.getClass().getSimpleName(), "onPause() ending");
  279. }
  280. @Override
  281. public void onActivityStopped(Activity activity) {
  282. Log_OC.d(activity.getClass().getSimpleName(), "onStop() ending");
  283. passCodeManager.onActivityStopped(activity);
  284. }
  285. @Override
  286. public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
  287. Log_OC.d(activity.getClass().getSimpleName(), "onSaveInstanceState(Bundle) starting");
  288. }
  289. @Override
  290. public void onActivityDestroyed(Activity activity) {
  291. Log_OC.d(activity.getClass().getSimpleName(), "onDestroy() ending");
  292. }
  293. });
  294. }
  295. private void initSecurityKeyManager() {
  296. SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
  297. SecurityKeyManagerConfig config = new SecurityKeyManagerConfig.Builder()
  298. .setEnableDebugLogging(BuildConfig.DEBUG)
  299. .build();
  300. securityKeyManager.init(this, config);
  301. }
  302. public static void initContactsBackup(UserAccountManager accountManager, BackgroundJobManager backgroundJobManager) {
  303. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(mContext.getContentResolver());
  304. List<User> users = accountManager.getAllUsers();
  305. for (User user : users) {
  306. if (arbitraryDataProvider.getBooleanValue(user.toPlatformAccount(), PREFERENCE_CONTACTS_AUTOMATIC_BACKUP)) {
  307. backgroundJobManager.schedulePeriodicContactsBackup(user);
  308. }
  309. }
  310. }
  311. private void insertConscrypt() {
  312. Security.insertProviderAt(Conscrypt.newProvider(), 1);
  313. try {
  314. Conscrypt.Version version = Conscrypt.version();
  315. Log_OC.i(TAG, "Using Conscrypt/"
  316. + version.major()
  317. + DOT
  318. + version.minor()
  319. + DOT + version.patch()
  320. + " for TLS");
  321. SSLEngine engine = SSLContext.getDefault().createSSLEngine();
  322. Log_OC.i(TAG, "Enabled protocols: " + Arrays.toString(engine.getEnabledProtocols()) + " }");
  323. Log_OC.i(TAG, "Enabled ciphers: " + Arrays.toString(engine.getEnabledCipherSuites()) + " }");
  324. } catch (NoSuchAlgorithmException e) {
  325. Log_OC.e(TAG, e.getMessage());
  326. }
  327. }
  328. @SuppressLint("ApplySharedPref") // commit is done on purpose to write immediately
  329. private void fixStoragePath() {
  330. if (!preferences.isStoragePathFixEnabled()) {
  331. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  332. StoragePoint[] storagePoints = DataStorageProvider.getInstance().getAvailableStoragePoints();
  333. String storagePath = preferences.getStoragePath("");
  334. if (TextUtils.isEmpty(storagePath)) {
  335. if (preferences.getLastSeenVersionCode() != 0) {
  336. // We already used the app, but no storage is set - fix that!
  337. preferences.setStoragePath(Environment.getExternalStorageDirectory().getAbsolutePath());
  338. preferences.removeKeysMigrationPreference();
  339. } else {
  340. // find internal storage path that's indexable
  341. boolean set = false;
  342. for (StoragePoint storagePoint : storagePoints) {
  343. if (storagePoint.getStorageType() == StoragePoint.StorageType.INTERNAL &&
  344. storagePoint.getPrivacyType() == StoragePoint.PrivacyType.PUBLIC) {
  345. preferences.setStoragePath(storagePoint.getPath());
  346. preferences.removeKeysMigrationPreference();
  347. set = true;
  348. break;
  349. }
  350. }
  351. if (!set) {
  352. for (StoragePoint storagePoint : storagePoints) {
  353. if (storagePoint.getPrivacyType() == StoragePoint.PrivacyType.PUBLIC) {
  354. preferences.setStoragePath(storagePoint.getPath());
  355. preferences.removeKeysMigrationPreference();
  356. set = true;
  357. break;
  358. }
  359. }
  360. }
  361. }
  362. preferences.setStoragePathFixEnabled(true);
  363. } else {
  364. preferences.removeKeysMigrationPreference();
  365. preferences.setStoragePathFixEnabled(true);
  366. }
  367. } else {
  368. if (TextUtils.isEmpty(storagePath)) {
  369. preferences.setStoragePath(Environment.getExternalStorageDirectory().getAbsolutePath());
  370. }
  371. preferences.removeKeysMigrationPreference();
  372. preferences.setStoragePathFixEnabled(true);
  373. }
  374. }
  375. }
  376. public static void initSyncOperations(
  377. final AppPreferences preferences,
  378. final UploadsStorageManager uploadsStorageManager,
  379. final UserAccountManager accountManager,
  380. final ConnectivityService connectivityService,
  381. final PowerManagementService powerManagementService,
  382. final BackgroundJobManager backgroundJobManager,
  383. final Clock clock
  384. ) {
  385. updateToAutoUpload();
  386. cleanOldEntries(clock);
  387. updateAutoUploadEntries(clock);
  388. if (getAppContext() != null) {
  389. if (PermissionUtil.checkSelfPermission(getAppContext(),
  390. Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
  391. splitOutAutoUploadEntries(clock);
  392. } else {
  393. preferences.setAutoUploadSplitEntriesEnabled(true);
  394. }
  395. }
  396. if (!preferences.isAutoUploadInitialized()) {
  397. backgroundJobManager.startImmediateFilesSyncJob(false, false);
  398. preferences.setAutoUploadInit(true);
  399. }
  400. FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, backgroundJobManager);
  401. FilesSyncHelper.restartJobsIfNeeded(
  402. uploadsStorageManager,
  403. accountManager,
  404. connectivityService,
  405. powerManagementService);
  406. backgroundJobManager.scheduleOfflineSync();
  407. ReceiversHelper.registerNetworkChangeReceiver(uploadsStorageManager,
  408. accountManager,
  409. connectivityService,
  410. powerManagementService);
  411. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  412. ReceiversHelper.registerPowerChangeReceiver(uploadsStorageManager,
  413. accountManager,
  414. connectivityService,
  415. powerManagementService);
  416. }
  417. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  418. ReceiversHelper.registerPowerSaveReceiver(uploadsStorageManager,
  419. accountManager,
  420. connectivityService,
  421. powerManagementService);
  422. }
  423. }
  424. public static void notificationChannels() {
  425. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O && getAppContext() != null) {
  426. Context context = getAppContext();
  427. NotificationManager notificationManager = (NotificationManager)
  428. context.getSystemService(Context.NOTIFICATION_SERVICE);
  429. if (notificationManager != null) {
  430. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD,
  431. R.string.notification_channel_download_name,
  432. R.string.notification_channel_download_description, context);
  433. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD,
  434. R.string.notification_channel_upload_name,
  435. R.string.notification_channel_upload_description, context);
  436. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_MEDIA,
  437. R.string.notification_channel_media_name,
  438. R.string.notification_channel_media_description, context);
  439. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_FILE_SYNC,
  440. R.string.notification_channel_file_sync_name,
  441. R.string.notification_channel_file_sync_description, context);
  442. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_FILE_OBSERVER,
  443. R.string.notification_channel_file_observer_name, R.string
  444. .notification_channel_file_observer_description, context);
  445. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_PUSH,
  446. R.string.notification_channel_push_name, R.string
  447. .notification_channel_push_description, context, NotificationManager.IMPORTANCE_DEFAULT);
  448. createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL, R.string
  449. .notification_channel_general_name, R.string.notification_channel_general_description,
  450. context, NotificationManager.IMPORTANCE_DEFAULT);
  451. } else {
  452. Log_OC.e(TAG, "Notification manager is null");
  453. }
  454. }
  455. }
  456. @RequiresApi(api = Build.VERSION_CODES.N)
  457. private static void createChannel(NotificationManager notificationManager,
  458. String channelId, int channelName,
  459. int channelDescription, Context context) {
  460. createChannel(notificationManager, channelId, channelName, channelDescription, context,
  461. NotificationManager.IMPORTANCE_LOW);
  462. }
  463. private static void createChannel(NotificationManager notificationManager,
  464. String channelId, int channelName,
  465. int channelDescription, Context context, int importance) {
  466. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O
  467. && getAppContext() != null
  468. && notificationManager.getNotificationChannel(channelId) == null) {
  469. CharSequence name = context.getString(channelName);
  470. String description = context.getString(channelDescription);
  471. NotificationChannel channel = new NotificationChannel(channelId, name, importance);
  472. channel.setDescription(description);
  473. channel.enableLights(false);
  474. channel.enableVibration(false);
  475. notificationManager.createNotificationChannel(channel);
  476. }
  477. }
  478. public static Context getAppContext() {
  479. return MainApp.mContext;
  480. }
  481. public static void setAppContext(Context context) {
  482. MainApp.mContext = context;
  483. }
  484. public static String getStoragePath() {
  485. return MainApp.storagePath;
  486. }
  487. public static void setStoragePath(String path) {
  488. MainApp.storagePath = path;
  489. }
  490. // Methods to obtain Strings referring app_name
  491. // From AccountAuthenticator
  492. // public static final String ACCOUNT_TYPE = "owncloud";
  493. public static String getAccountType(Context context) {
  494. return context.getResources().getString(R.string.account_type);
  495. }
  496. // From AccountAuthenticator
  497. // public static final String AUTHORITY = "org.owncloud";
  498. public static String getAuthority() {
  499. return getAppContext().getResources().getString(R.string.authority);
  500. }
  501. // From AccountAuthenticator
  502. // public static final String AUTH_TOKEN_TYPE = "org.owncloud";
  503. public static String getAuthTokenType() {
  504. return getAppContext().getResources().getString(R.string.authority);
  505. }
  506. // From ProviderMeta
  507. // public static final String DB_FILE = "owncloud.db";
  508. public static String getDBFile() {
  509. return getAppContext().getResources().getString(R.string.db_file);
  510. }
  511. // From ProviderMeta
  512. // private final String mDatabaseName = "ownCloud";
  513. public static String getDBName() {
  514. return getAppContext().getResources().getString(R.string.db_name);
  515. }
  516. /**
  517. * name of data_folder, e.g., "owncloud"
  518. */
  519. public static String getDataFolder() {
  520. return getAppContext().getResources().getString(R.string.data_folder);
  521. }
  522. public static void showOnlyFilesOnDevice(boolean state) {
  523. mOnlyOnDevice = state;
  524. }
  525. public static boolean isOnlyOnDevice() {
  526. return mOnlyOnDevice;
  527. }
  528. public static String getUserAgent() {
  529. // Mozilla/5.0 (Android) Nextcloud-android/2.1.0
  530. return getUserAgent(R.string.nextcloud_user_agent);
  531. }
  532. // user agent
  533. private static String getUserAgent(@StringRes int agent) {
  534. String appString = getAppContext().getResources().getString(agent);
  535. String packageName = getAppContext().getPackageName();
  536. String version = "";
  537. try {
  538. PackageInfo pInfo = getAppContext().getPackageManager().getPackageInfo(packageName, 0);
  539. if (pInfo != null) {
  540. version = pInfo.versionName;
  541. }
  542. } catch (PackageManager.NameNotFoundException e) {
  543. Log_OC.e(TAG, "Trying to get packageName", e.getCause());
  544. }
  545. return String.format(appString, version);
  546. }
  547. private static void updateToAutoUpload() {
  548. Context context = getAppContext();
  549. AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  550. if (preferences.instantPictureUploadEnabled() || preferences.instantVideoUploadEnabled()) {
  551. preferences.removeLegacyPreferences();
  552. // show info pop-up
  553. try {
  554. new AlertDialog.Builder(context, R.style.Theme_ownCloud_Dialog)
  555. .setTitle(R.string.drawer_synced_folders)
  556. .setMessage(R.string.synced_folders_new_info)
  557. .setPositiveButton(R.string.drawer_open, (dialog, which) -> {
  558. // show Auto Upload
  559. Intent folderSyncIntent = new Intent(context, SyncedFoldersActivity.class);
  560. dialog.dismiss();
  561. context.startActivity(folderSyncIntent);
  562. })
  563. .setNegativeButton(R.string.drawer_close, (dialog, which) -> dialog.dismiss())
  564. .setIcon(R.drawable.nav_synced_folders)
  565. .show();
  566. } catch (WindowManager.BadTokenException e) {
  567. Log_OC.i(TAG, "Error showing Auto Upload Update dialog, so skipping it: " + e.getMessage());
  568. }
  569. }
  570. }
  571. private static void updateAutoUploadEntries(Clock clock) {
  572. // updates entries to reflect their true paths
  573. Context context = getAppContext();
  574. AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  575. if (!preferences.isAutoUploadPathsUpdateEnabled()) {
  576. SyncedFolderProvider syncedFolderProvider =
  577. new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
  578. syncedFolderProvider.updateAutoUploadPaths(mContext);
  579. }
  580. }
  581. private static void splitOutAutoUploadEntries(Clock clock) {
  582. Context context = getAppContext();
  583. AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  584. if (!preferences.isAutoUploadSplitEntriesEnabled()) {
  585. // magic to split out existing synced folders in two when needed
  586. // otherwise, we migrate them to their proper type (image or video)
  587. Log_OC.i(TAG, "Migrate synced_folders records for image/video split");
  588. ContentResolver contentResolver = context.getContentResolver();
  589. SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
  590. final List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
  591. final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
  592. ArrayList<Long> idsToDelete = new ArrayList<>();
  593. List<SyncedFolder> syncedFolders = syncedFolderProvider.getSyncedFolders();
  594. long primaryKey;
  595. SyncedFolder newSyncedFolder;
  596. for (SyncedFolder syncedFolder : syncedFolders) {
  597. idsToDelete.add(syncedFolder.getId());
  598. Log_OC.i(TAG, "Migration check for synced_folders record: "
  599. + syncedFolder.getId() + " - " + syncedFolder.getLocalPath());
  600. for (MediaFolder imageMediaFolder : imageMediaFolders) {
  601. if (imageMediaFolder.absolutePath.equals(syncedFolder.getLocalPath())) {
  602. newSyncedFolder = (SyncedFolder) syncedFolder.clone();
  603. newSyncedFolder.setType(MediaFolderType.IMAGE);
  604. primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
  605. Log_OC.i(TAG, "Migrated image synced_folders record: "
  606. + primaryKey + " - " + newSyncedFolder.getLocalPath());
  607. break;
  608. }
  609. }
  610. for (MediaFolder videoMediaFolder : videoMediaFolders) {
  611. if (videoMediaFolder.absolutePath.equals(syncedFolder.getLocalPath())) {
  612. newSyncedFolder = (SyncedFolder) syncedFolder.clone();
  613. newSyncedFolder.setType(MediaFolderType.VIDEO);
  614. primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
  615. Log_OC.i(TAG, "Migrated video synced_folders record: "
  616. + primaryKey + " - " + newSyncedFolder.getLocalPath());
  617. break;
  618. }
  619. }
  620. }
  621. for (long id : idsToDelete) {
  622. Log_OC.i(TAG, "Removing legacy synced_folders record: " + id);
  623. syncedFolderProvider.deleteSyncedFolder(id);
  624. }
  625. preferences.setAutoUploadSplitEntriesEnabled(true);
  626. }
  627. }
  628. private static void cleanOldEntries(Clock clock) {
  629. // previous versions of application created broken entries in the SyncedFolderProvider
  630. // database, and this cleans all that and leaves 1 (newest) entry per synced folder
  631. Context context = getAppContext();
  632. AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  633. if (!preferences.isLegacyClean()) {
  634. SyncedFolderProvider syncedFolderProvider =
  635. new SyncedFolderProvider(context.getContentResolver(), preferences, clock);
  636. List<SyncedFolder> syncedFolderList = syncedFolderProvider.getSyncedFolders();
  637. Map<Pair<String, String>, Long> syncedFolders = new HashMap<>();
  638. ArrayList<Long> ids = new ArrayList<>();
  639. for (SyncedFolder syncedFolder : syncedFolderList) {
  640. Pair<String, String> checkPair = new Pair<>(syncedFolder.getAccount(), syncedFolder.getLocalPath());
  641. if (syncedFolders.containsKey(checkPair)) {
  642. if (syncedFolder.getId() > syncedFolders.get(checkPair)) {
  643. syncedFolders.put(checkPair, syncedFolder.getId());
  644. }
  645. } else {
  646. syncedFolders.put(checkPair, syncedFolder.getId());
  647. }
  648. }
  649. ids.addAll(syncedFolders.values());
  650. if (ids.size() > 0) {
  651. int deletedCount = syncedFolderProvider.deleteSyncedFoldersNotInList(ids);
  652. if (deletedCount > 0) {
  653. preferences.setLegacyClean(true);
  654. }
  655. } else {
  656. preferences.setLegacyClean(true);
  657. }
  658. }
  659. }
  660. @Override
  661. public AndroidInjector<Object> androidInjector() {
  662. return dispatchingAndroidInjector;
  663. }
  664. public static void setAppTheme(DarkMode mode) {
  665. switch (mode) {
  666. case LIGHT:
  667. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
  668. break;
  669. case DARK:
  670. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
  671. break;
  672. case SYSTEM:
  673. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
  674. break;
  675. }
  676. }
  677. }