RetroActivityCommon.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. package com.retroarch.browser.retroactivity;
  2. import com.xugame.BuildConfig;
  3. import com.xugame.app.EnvironmentCallback;
  4. import com.xugame.app.EnvironmentCallbackCmd;
  5. import com.xugame.app.JoypadManager;
  6. import com.xugame.bean.InputDescriptorBean;
  7. import com.xugame.bean.SystemAVInfo;
  8. import com.xugame.gameconsole.playcore.PlayCoreManager;
  9. import com.xugame.gameconsole.preferences.UserPreferences;
  10. import com.xugame.gameconsole.util.DebugUtil;
  11. import android.annotation.TargetApi;
  12. import android.app.Activity;
  13. import android.app.NativeActivity;
  14. import android.content.res.Configuration;
  15. import android.content.Context;
  16. import android.content.SharedPreferences;
  17. import android.content.Intent;
  18. import android.content.IntentFilter;
  19. import android.content.pm.ActivityInfo;
  20. import android.media.AudioAttributes;
  21. import android.os.Bundle;
  22. import android.os.CountDownTimer;
  23. import android.os.storage.StorageManager;
  24. import android.os.storage.StorageVolume;
  25. import android.system.Os;
  26. import android.view.HapticFeedbackConstants;
  27. import android.view.InputDevice;
  28. import android.view.InputQueue;
  29. import android.view.Surface;
  30. import android.view.SurfaceHolder;
  31. import android.view.WindowManager;
  32. import android.app.UiModeManager;
  33. import android.os.BatteryManager;
  34. import android.os.Build;
  35. import android.os.PowerManager;
  36. import android.os.Vibrator;
  37. import android.os.VibrationEffect;
  38. import android.util.Log;
  39. import java.io.File;
  40. import java.util.ArrayList;
  41. import java.util.Arrays;
  42. import java.util.List;
  43. import java.util.concurrent.CountDownLatch;
  44. import java.util.Locale;
  45. /**
  46. * Class which provides common methods for RetroActivity related classes.
  47. */
  48. //public class RetroActivityCommon extends NativeActivity
  49. public abstract class RetroActivityCommon extends NativeActivity
  50. {
  51. private static final String TAG = "RetroActivityCommonTAG";
  52. static { System.loadLibrary("retroarch-activity"); }
  53. public static int FRONTEND_POWERSTATE_NONE = 0;
  54. public static int FRONTEND_POWERSTATE_NO_SOURCE = 1;
  55. public static int FRONTEND_POWERSTATE_CHARGING = 2;
  56. public static int FRONTEND_POWERSTATE_CHARGED = 3;
  57. public static int FRONTEND_POWERSTATE_ON_POWER_SOURCE = 4;
  58. public static int FRONTEND_ORIENTATION_0 = 0;
  59. public static int FRONTEND_ORIENTATION_90 = 1;
  60. public static int FRONTEND_ORIENTATION_180 = 2;
  61. public static int FRONTEND_ORIENTATION_270 = 3;
  62. public static int RETRO_RUMBLE_STRONG = 0;
  63. public static int RETRO_RUMBLE_WEAK = 1;
  64. public boolean sustainedPerformanceMode = true;
  65. public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
  66. protected EnvironmentCallback callback = null;
  67. public abstract void showExitDialog();
  68. public void environmentCallback(int cmd, Object data) {
  69. if (callback == null) return;
  70. Log.i(TAG, "environmentCallback");
  71. if (cmd == EnvironmentCallbackCmd.SET_INPUT_DESCRIPTORS) {
  72. callback.setInputDescriptors((InputDescriptorBean[]) data);
  73. } else if (cmd == EnvironmentCallbackCmd.SET_SYSTEM_AV_INFO) {
  74. callback.setSystemAVInfo((SystemAVInfo) data);
  75. } else if (cmd == EnvironmentCallbackCmd.SET_GEOMETRY) {
  76. callback.setGeometry((SystemAVInfo.GameGeometry) data);
  77. }
  78. }
  79. @Override
  80. protected void onCreate(Bundle savedInstanceState) {
  81. cleanupSymlinks();
  82. updateSymlinks();
  83. PlayCoreManager.getInstance().onCreate(this);
  84. super.onCreate(savedInstanceState);
  85. // startCuntDown();
  86. }
  87. private native void registerBeans();
  88. private native void unregisterBeans();
  89. @Override
  90. protected void onStart() {
  91. super.onStart();
  92. registerBeans();
  93. }
  94. @Override
  95. protected void onStop() {
  96. super.onStop();
  97. }
  98. private CountDownTimer mDownTimer;
  99. private void startCuntDown(){
  100. mDownTimer=new CountDownTimer(4000,1000) {
  101. @Override
  102. public void onTick(long millisUntilFinished) {
  103. DebugUtil.i(TAG,"onTick"+millisUntilFinished);
  104. }
  105. @Override
  106. public void onFinish() {
  107. finish();
  108. }
  109. };
  110. mDownTimer.start();
  111. }
  112. @Override
  113. protected void onDestroy() {
  114. PlayCoreManager.getInstance().onDestroy();
  115. unregisterBeans();
  116. super.onDestroy();
  117. }
  118. public void doVibrate(int id, int effect, int strength, int oneShot)
  119. {
  120. Vibrator vibrator = null;
  121. int repeat = 0;
  122. long[] pattern = {0, 16};
  123. int[] strengths = {0, strength};
  124. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  125. if (id == -1)
  126. vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
  127. else
  128. {
  129. InputDevice dev = InputDevice.getDevice(id);
  130. if (dev != null)
  131. vibrator = dev.getVibrator();
  132. }
  133. }
  134. if (vibrator == null)
  135. return;
  136. if (strength == 0) {
  137. vibrator.cancel();
  138. return;
  139. }
  140. if (oneShot > 0)
  141. repeat = -1;
  142. else
  143. pattern[1] = 1000;
  144. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  145. if (id >= 0)
  146. Log.i("RetroActivity", "Vibrate id " + id + ": strength " + strength);
  147. vibrator.vibrate(VibrationEffect.createWaveform(pattern, strengths, repeat), new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_GAME).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build());
  148. }else{
  149. vibrator.vibrate(pattern, repeat);
  150. }
  151. }
  152. public void doHapticFeedback(int effect)
  153. {
  154. getWindow().getDecorView().performHapticFeedback(effect,
  155. HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
  156. Log.i("RetroActivity", "Haptic Feedback effect " + effect);
  157. }
  158. // Exiting cleanly from NDK seems to be nearly impossible.
  159. // Have to use exit(0) to avoid weird things happening, even with runOnUiThread() approaches.
  160. // Use a separate JNI function to explicitly trigger the readback.
  161. public void onRetroArchExit()
  162. {
  163. DebugUtil.i(TAG,"onRetroArchExit");
  164. finish();
  165. }
  166. public int getVolumeCount()
  167. {
  168. int ret = 0;
  169. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
  170. StorageManager storageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);
  171. List<StorageVolume> storageVolumeList = storageManager.getStorageVolumes();
  172. for (int i = 0; i < storageVolumeList.size(); i++) {
  173. ret++;
  174. }
  175. Log.i("RetroActivity", "volume count: " + ret);
  176. }
  177. return (int)ret;
  178. }
  179. public String getVolumePath(String input)
  180. {
  181. String ret = "";
  182. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
  183. int index = Integer.valueOf(input);
  184. int j = 0;
  185. StorageManager storageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);
  186. List<StorageVolume> storageVolumeList = storageManager.getStorageVolumes();
  187. for (int i = 0; i < storageVolumeList.size(); i++) {
  188. if (i == j) {
  189. ret = String.valueOf(storageVolumeList.get(index).getDirectory());
  190. }
  191. }
  192. Log.i("RetroActivity", "volume path: " + ret);
  193. }
  194. return ret;
  195. }
  196. // https://stackoverflow.com/questions/4553650/how-to-check-device-natural-default-orientation-on-android-i-e-get-landscape/4555528#4555528
  197. public int getDeviceDefaultOrientation() {
  198. WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
  199. Configuration config = getResources().getConfiguration();
  200. int rotation = windowManager.getDefaultDisplay().getRotation();
  201. if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) &&
  202. config.orientation == Configuration.ORIENTATION_LANDSCAPE)
  203. || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) &&
  204. config.orientation == Configuration.ORIENTATION_PORTRAIT))
  205. {
  206. return Configuration.ORIENTATION_LANDSCAPE;
  207. }else{
  208. return Configuration.ORIENTATION_PORTRAIT;
  209. }
  210. }
  211. public void setScreenOrientation(int orientation)
  212. {
  213. int naturalOrientation = getDeviceDefaultOrientation();
  214. int newOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
  215. // We assume no device has a natural orientation that is reversed
  216. switch (naturalOrientation) {
  217. case Configuration.ORIENTATION_PORTRAIT:
  218. {
  219. if (orientation == FRONTEND_ORIENTATION_0) {
  220. newOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
  221. }else if (orientation == FRONTEND_ORIENTATION_90) {
  222. newOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
  223. }else if (orientation == FRONTEND_ORIENTATION_180) {
  224. newOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
  225. }else if (orientation == FRONTEND_ORIENTATION_270) {
  226. newOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
  227. }
  228. break;
  229. }
  230. case Configuration.ORIENTATION_LANDSCAPE:
  231. {
  232. if (orientation == FRONTEND_ORIENTATION_0) {
  233. newOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
  234. }else if (orientation == FRONTEND_ORIENTATION_90) {
  235. newOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
  236. }else if (orientation == FRONTEND_ORIENTATION_180) {
  237. newOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
  238. }else if (orientation == FRONTEND_ORIENTATION_270) {
  239. newOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
  240. }
  241. break;
  242. }
  243. }
  244. screenOrientation = newOrientation;
  245. Log.i("RetroActivity", "setting new orientation to " + screenOrientation);
  246. runOnUiThread(new Runnable() {
  247. @Override
  248. public void run() {
  249. setRequestedOrientation(screenOrientation);
  250. }
  251. });
  252. }
  253. public String getUserLanguageString()
  254. {
  255. String lang = Locale.getDefault().getLanguage();
  256. String country = Locale.getDefault().getCountry();
  257. if (lang.length() == 0)
  258. return "en";
  259. if (country.length() == 0)
  260. return lang;
  261. return lang + '_' + country;
  262. }
  263. @TargetApi(24)
  264. public void setSustainedPerformanceMode(boolean on)
  265. {
  266. // sustainedPerformanceMode = on;
  267. //
  268. // if (Build.VERSION.SDK_INT >= 24) {
  269. // if (isSustainedPerformanceModeSupported()) {
  270. // final CountDownLatch latch = new CountDownLatch(1);
  271. //
  272. // runOnUiThread(new Runnable() {
  273. // @Override
  274. // public void run() {
  275. // Log.i("RetroActivity", "setting sustained performance mode to " + sustainedPerformanceMode);
  276. //
  277. // getWindow().setSustainedPerformanceMode(sustainedPerformanceMode);
  278. //
  279. // latch.countDown();
  280. // }
  281. // });
  282. //
  283. // try {
  284. // latch.await();
  285. // }catch(InterruptedException e) {
  286. // e.printStackTrace();
  287. // }
  288. // }
  289. // }
  290. }
  291. @TargetApi(24)
  292. public boolean isSustainedPerformanceModeSupported()
  293. {
  294. boolean supported = false;
  295. if (Build.VERSION.SDK_INT >= 24)
  296. {
  297. PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
  298. if (powerManager.isSustainedPerformanceModeSupported())
  299. supported = true;
  300. }
  301. Log.i("RetroActivity", "isSustainedPerformanceModeSupported? " + supported);
  302. return supported;
  303. }
  304. public int getBatteryLevel()
  305. {
  306. IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
  307. // This doesn't actually register anything (or need to) because we know this particular intent is sticky and we do not specify a BroadcastReceiver anyway
  308. Intent batteryStatus = registerReceiver(null, ifilter);
  309. int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
  310. int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
  311. float percent = ((float)level / (float)scale) * 100.0f;
  312. Log.i("RetroActivity", "battery: level = " + level + ", scale = " + scale + ", percent = " + percent);
  313. return (int)percent;
  314. }
  315. public int getPowerstate()
  316. {
  317. IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
  318. // This doesn't actually register anything (or need to) because we know this particular intent is sticky and we do not specify a BroadcastReceiver anyway
  319. Intent batteryStatus = registerReceiver(null, ifilter);
  320. int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
  321. boolean hasBattery = batteryStatus.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false);
  322. boolean isCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING);
  323. boolean isCharged = (status == BatteryManager.BATTERY_STATUS_FULL);
  324. int powerstate = FRONTEND_POWERSTATE_NONE;
  325. if (isCharged)
  326. powerstate = FRONTEND_POWERSTATE_CHARGED;
  327. else if (isCharging)
  328. powerstate = FRONTEND_POWERSTATE_CHARGING;
  329. else if (!hasBattery)
  330. powerstate = FRONTEND_POWERSTATE_NO_SOURCE;
  331. else
  332. powerstate = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
  333. Log.i("RetroActivity", "power state = " + powerstate);
  334. return powerstate;
  335. }
  336. public boolean isAndroidTV()
  337. {
  338. Configuration config = getResources().getConfiguration();
  339. UiModeManager uiModeManager = (UiModeManager)getSystemService(UI_MODE_SERVICE);
  340. if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION)
  341. {
  342. Log.i("RetroActivity", "isAndroidTV == true");
  343. return true;
  344. }
  345. else
  346. {
  347. Log.i("RetroActivity", "isAndroidTV == false");
  348. return false;
  349. }
  350. }
  351. //call by jni
  352. public void openGameDialog() {
  353. DebugUtil.i(TAG,"openGameDialog");
  354. showExitDialog();
  355. }
  356. @Override
  357. public void onConfigurationChanged(Configuration newConfig) {
  358. int oldOrientation = 0;
  359. boolean hasOldOrientation = false;
  360. super.onConfigurationChanged(newConfig);
  361. Log.i("RetroActivity", "onConfigurationChanged: orientation is now " + newConfig.orientation);
  362. SharedPreferences prefs = UserPreferences.getPreferences(this);
  363. SharedPreferences.Editor edit = prefs.edit();
  364. hasOldOrientation = prefs.contains("ORIENTATION");
  365. if (hasOldOrientation)
  366. oldOrientation = prefs.getInt("ORIENTATION", 0);
  367. edit.putInt("ORIENTATION", newConfig.orientation);
  368. edit.apply();
  369. Log.i("RetroActivity", "hasOldOrientation? " + hasOldOrientation + " newOrientation: " + newConfig.orientation + " oldOrientation: " + oldOrientation);
  370. }
  371. /**
  372. * Checks if this version of RetroArch is a Play Store build.
  373. *
  374. * @return true if this is a Play Store build, false otherwise
  375. */
  376. public boolean isPlayStoreBuild() {
  377. Log.i("RetroActivity", "isPlayStoreBuild: " + BuildConfig.PLAY_STORE_BUILD);
  378. return BuildConfig.PLAY_STORE_BUILD;
  379. }
  380. /**
  381. * Gets the list of available cores that can be downloaded as Dynamic Feature Modules.
  382. *
  383. * @return the list of available cores
  384. */
  385. public String[] getAvailableCores() {
  386. int id = getResources().getIdentifier("module_names_" + Build.CPU_ABI.replace('-', '_'), "array", getPackageName());
  387. String[] returnVal = getResources().getStringArray(id);
  388. Log.i("RetroActivity", "getAvailableCores: " + Arrays.toString(returnVal));
  389. return returnVal;
  390. }
  391. /**
  392. * Gets the list of cores that are currently installed as Dynamic Feature Modules.
  393. *
  394. * @return the list of installed cores
  395. */
  396. public String[] getInstalledCores() {
  397. String[] modules = PlayCoreManager.getInstance().getInstalledModules();
  398. List<String> cores = new ArrayList<>();
  399. List<String> availableCores = Arrays.asList(getAvailableCores());
  400. SharedPreferences prefs = UserPreferences.getPreferences(this);
  401. for(int i = 0; i < modules.length; i++) {
  402. String coreName = unsanitizeCoreName(modules[i]);
  403. if(!prefs.getBoolean("core_deleted_" + coreName, false)
  404. && availableCores.contains(coreName)) {
  405. cores.add(coreName);
  406. }
  407. }
  408. String[] returnVal = cores.toArray(new String[0]);
  409. Log.i("RetroActivity", "getInstalledCores: " + Arrays.toString(returnVal));
  410. return returnVal;
  411. }
  412. /**
  413. * Asks the system to download a core.
  414. *
  415. * @param coreName Name of the core to install
  416. */
  417. public void downloadCore(final String coreName) {
  418. Log.i("RetroActivity", "downloadCore: " + coreName);
  419. SharedPreferences prefs = UserPreferences.getPreferences(this);
  420. prefs.edit().remove("core_deleted_" + coreName).apply();
  421. PlayCoreManager.getInstance().downloadCore(coreName);
  422. }
  423. /**
  424. * Asks the system to delete a core.
  425. *
  426. * Note that the actual module deletion will not happen immediately (the OS will delete
  427. * it whenever it feels like it), but the symlink will still be immediately removed.
  428. *
  429. * @param coreName Name of the core to delete
  430. */
  431. public void deleteCore(String coreName) {
  432. Log.i("RetroActivity", "deleteCore: " + coreName);
  433. String newFilename = getCorePath() + coreName + "_libretro_android.so";
  434. new File(newFilename).delete();
  435. SharedPreferences prefs = UserPreferences.getPreferences(this);
  436. prefs.edit().putBoolean("core_deleted_" + coreName, true).apply();
  437. PlayCoreManager.getInstance().deleteCore(coreName);
  438. }
  439. /////////////// JNI methods ///////////////
  440. /**
  441. * 点击返回时,退出库
  442. */
  443. public native void exitLocalGame();
  444. /**
  445. * 保存状态
  446. * @param stateIndex 状态索引
  447. * @return 成功返回true,失败返回false
  448. */
  449. public native boolean saveState(int stateIndex);
  450. /**
  451. * 加载状态
  452. * @param stateIndex 状态索引
  453. * @return 成功返回true,失败返回false
  454. */
  455. public native boolean loadState(int stateIndex);
  456. /**
  457. * 设置分辨率
  458. */
  459. public native void setAspectRatio(int aspectRatioIndex);
  460. /**
  461. * 设置分辨率自定义时,的位置和大小
  462. */
  463. public native void setCustomX(int x);
  464. public native void setCustomY(int y);
  465. public native void setCustomWidth(int width);
  466. public native void setCustomHeight(int height);
  467. public native void setCustomViewPort(int x, int y, int width, int height);
  468. /**
  469. * 切换渲染路径
  470. */
  471. public native void switchFilter(String path);
  472. /**
  473. * 切换渲染路径
  474. */
  475. public native void setInputKeys(int port, int keyBindId, int key);
  476. /**
  477. * 保存配置
  478. */
  479. public native void saveOverrideConfig();
  480. public native void gameDialogClosed();
  481. public native void setJoystickBindNative(int port, int[] joyKeys, int[] joyAxiss);
  482. /////////////// Private methods ///////////////
  483. /**
  484. * Sanitizes a core name so that it can be used when dealing with
  485. * Dynamic Feature Modules. Needed because Gradle modules cannot use
  486. * dashes, but we have at least one core name ("mesen-s") that uses them.
  487. *
  488. * @param coreName Name of the core to sanitize.
  489. * @return The sanitized core name.
  490. */
  491. public String sanitizeCoreName(String coreName) {
  492. return "core_" + coreName.replace('-', '_');
  493. }
  494. /**
  495. * Unsanitizes a core name from its module name.
  496. *
  497. * @param coreName Name of the core to unsanitize.
  498. * @return The unsanitized core name.
  499. */
  500. public String unsanitizeCoreName(String coreName) {
  501. if(coreName.equals("core_mesen_s")) {
  502. return "mesen-s";
  503. }
  504. return coreName.substring(5);
  505. }
  506. /**
  507. * Gets the path to the RetroArch cores directory.
  508. *
  509. * @return The path to the RetroArch cores directory
  510. */
  511. private String getCorePath() {
  512. String path = getApplicationInfo().dataDir + "/cores/";
  513. new File(path).mkdirs();
  514. return path;
  515. }
  516. /**
  517. * Cleans up existing symlinks before new ones are created.
  518. */
  519. private void cleanupSymlinks() {
  520. if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
  521. File[] files = new File(getCorePath()).listFiles();
  522. for(int i = 0; i < files.length; i++) {
  523. try {
  524. Os.readlink(files[i].getAbsolutePath());
  525. files[i].delete();
  526. } catch (Exception e) {
  527. // File is not a symlink, so don't delete.
  528. }
  529. }
  530. }
  531. /**
  532. * Triggers a symlink update in the known places that Dynamic Feature Modules
  533. * are installed to.
  534. */
  535. public void updateSymlinks() {
  536. if(!isPlayStoreBuild()) return;
  537. traverseFilesystem(getFilesDir());
  538. traverseFilesystem(new File(getApplicationInfo().nativeLibraryDir));
  539. }
  540. /**
  541. * Traverse the filesystem, looking for native libraries.
  542. * Symlinks any libraries it finds to the main RetroArch "cores" folder,
  543. * updating any existing symlinks with the correct path to the native libraries.
  544. *
  545. * This is necessary because Dynamic Feature Modules are first downloaded
  546. * and installed to a temporary location on disk, before being moved
  547. * to a more permanent location by the system at a later point.
  548. *
  549. * This could probably be done in native code instead, if that's preferred.
  550. *
  551. * @param file The parent directory of the tree to traverse.
  552. */
  553. private void traverseFilesystem(File file) {
  554. if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
  555. File[] list = file.listFiles();
  556. if(list == null) return;
  557. List<String> availableCores = Arrays.asList(getAvailableCores());
  558. // Check each file in a directory to see if it's a native library.
  559. for(int i = 0; i < list.length; i++) {
  560. File child = list[i];
  561. String name = child.getName();
  562. if(name.startsWith("lib") && name.endsWith(".so") && !name.contains("retroarch-activity")) {
  563. // Found a native library!
  564. String core = name.subSequence(3, name.length() - 3).toString();
  565. String filename = child.getAbsolutePath();
  566. SharedPreferences prefs = UserPreferences.getPreferences(this);
  567. if(!prefs.getBoolean("core_deleted_" + core, false)
  568. && availableCores.contains(core)) {
  569. // Generate the destination filename and delete any existing symlinks / cores
  570. String newFilename = getCorePath() + core + "_libretro_android.so";
  571. new File(newFilename).delete();
  572. try {
  573. Os.symlink(filename, newFilename);
  574. } catch (Exception e) {
  575. // Symlink failed to be created. Should never happen.
  576. }
  577. }
  578. } else if(file.isDirectory()) {
  579. // Found another directory, so traverse it
  580. traverseFilesystem(child);
  581. }
  582. }
  583. }
  584. }