diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle index 089c96a055..c53a0fa3d4 100644 --- a/Source/Android/app/build.gradle +++ b/Source/Android/app/build.gradle @@ -18,7 +18,7 @@ android { // TODO If this is ever modified, change application_id in strings.xml applicationId "org.dolphinemu.dolphinemu" minSdkVersion 21 - targetSdkVersion 21 + targetSdkVersion 25 // TODO This should be set to the Buildbot build number for release builds, and be "1" for debug builds. versionCode 13 @@ -72,13 +72,13 @@ android { } dependencies { - compile 'com.android.support:support-v13:25.2.0' - compile 'com.android.support:cardview-v7:25.2.0' - compile 'com.android.support:recyclerview-v7:25.2.0' - compile 'com.android.support:design:25.2.0' + compile 'com.android.support:support-v13:25.3.0' + compile 'com.android.support:cardview-v7:25.3.0' + compile 'com.android.support:recyclerview-v7:25.3.0' + compile 'com.android.support:design:25.3.0' // Android TV UI libraries. - compile 'com.android.support:leanback-v17:25.2.0' + compile 'com.android.support:leanback-v17:25.3.0' // For showing the banner as a circle a-la Material Design Guidelines compile 'de.hdodenhof:circleimageview:2.1.0' diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index 57923e0e0e..14e9d1758a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -1,6 +1,7 @@ package org.dolphinemu.dolphinemu.ui.main; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; @@ -13,6 +14,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity; @@ -20,6 +22,7 @@ import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter; import org.dolphinemu.dolphinemu.model.GameProvider; import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView; import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity; +import org.dolphinemu.dolphinemu.utils.PermissionsHandler; import org.dolphinemu.dolphinemu.utils.StartupHandler; /** @@ -45,9 +48,6 @@ public final class MainActivity extends AppCompatActivity implements MainView setSupportActionBar(mToolbar); - PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(getFragmentManager(), this); - - mViewPager.setAdapter(platformPagerAdapter); mTabLayout.setupWithViewPager(mViewPager); // Set up the FAB. @@ -66,6 +66,14 @@ public final class MainActivity extends AppCompatActivity implements MainView // TODO Split some of this stuff into Application.onCreate() if (savedInstanceState == null) StartupHandler.HandleInit(this); + + if (PermissionsHandler.hasWriteAccess(this)) + { + PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(getFragmentManager(), this); + mViewPager.setAdapter(platformPagerAdapter); + } else { + mViewPager.setVisibility(View.INVISIBLE); + } } // TODO: Replace with a ButterKnife injection. @@ -145,6 +153,28 @@ public final class MainActivity extends AppCompatActivity implements MainView mPresenter.handleActivityResult(requestCode, resultCode); } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + switch (requestCode) { + case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + StartupHandler.copyAssetsIfNeeded(this); + + PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(getFragmentManager(), this); + mViewPager.setAdapter(platformPagerAdapter); + mTabLayout.setupWithViewPager(mViewPager); + mViewPager.setVisibility(View.VISIBLE); + } else { + Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) + .show(); + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + break; + } + } + /** * Called by the framework whenever any actionbar/toolbar icon is clicked. * diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index a003603b1a..3f235fa1c4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -3,6 +3,7 @@ package org.dolphinemu.dolphinemu.ui.main; import android.app.Activity; import android.app.FragmentManager; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; import android.support.v17.leanback.app.BrowseFragment; @@ -16,6 +17,7 @@ import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; +import android.widget.Toast; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity; @@ -25,6 +27,7 @@ import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter; import org.dolphinemu.dolphinemu.model.Game; import org.dolphinemu.dolphinemu.model.TvSettingsItem; import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity; +import org.dolphinemu.dolphinemu.utils.PermissionsHandler; import org.dolphinemu.dolphinemu.utils.StartupHandler; import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; @@ -148,14 +151,31 @@ public final class TvMainActivity extends Activity implements MainView mPresenter.handleActivityResult(requestCode, resultCode); } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + switch (requestCode) { + case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + StartupHandler.copyAssetsIfNeeded(this); + loadGames(); + } else { + Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) + .show(); + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + break; + } + } + private void buildRowsAdapter() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); - // For each platform - for (int platformIndex = 0; platformIndex <= Game.PLATFORM_ALL; ++platformIndex) + if (PermissionsHandler.hasWriteAccess(this)) { - mPresenter.loadGames(platformIndex); + loadGames(); } mRowsAdapter.add(buildSettingsRow()); @@ -163,6 +183,13 @@ public final class TvMainActivity extends Activity implements MainView mBrowseFragment.setAdapter(mRowsAdapter); } + private void loadGames() { + // For each platform + for (int platformIndex = 0; platformIndex <= Game.PLATFORM_ALL; ++platformIndex) { + mPresenter.loadGames(platformIndex); + } + } + private ListRow buildGamesRow(int platform, Cursor games) { // Create an adapter for this row. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PermissionsHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PermissionsHandler.java new file mode 100644 index 0000000000..84ea110884 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PermissionsHandler.java @@ -0,0 +1,71 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.widget.Toast; + +import org.dolphinemu.dolphinemu.R; + +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; + +public class PermissionsHandler { + public static final int REQUEST_CODE_WRITE_PERMISSION = 500; + + @TargetApi(Build.VERSION_CODES.M) + public static boolean checkWritePermission(final Activity activity) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE); + + if (hasWritePermission != PackageManager.PERMISSION_GRANTED) { + if (activity.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) { + showMessageOKCancel(activity, activity.getString(R.string.write_permission_needed), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + activity.requestPermissions(new String[] {WRITE_EXTERNAL_STORAGE}, + REQUEST_CODE_WRITE_PERMISSION); + } + }); + return false; + } + + activity.requestPermissions(new String[] {WRITE_EXTERNAL_STORAGE}, + REQUEST_CODE_WRITE_PERMISSION); + return false; + } + + return true; + } + + public static boolean hasWriteAccess(Activity activity) { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE); + return hasWritePermission == PackageManager.PERMISSION_GRANTED; + } + + return true; + } + + private static void showMessageOKCancel(final Activity activity, String message, DialogInterface.OnClickListener okListener) { + new AlertDialog.Builder(activity) + .setMessage(message) + .setPositiveButton(android.R.string.ok, okListener) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Toast.makeText(activity, R.string.write_permission_needed, Toast.LENGTH_SHORT) + .show(); + } + }) + .create() + .show(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java index 51b4553088..2e31f405d0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java @@ -17,15 +17,9 @@ public final class StartupHandler { NativeLibrary.SetUserDirectory(""); // Auto-Detect - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(parent); - boolean assetsCopied = preferences.getBoolean("assetsCopied", false); - // Only perform these extensive copy operations once. - if (!assetsCopied) - { - // Copy assets into appropriate locations. - Intent copyAssets = new Intent(parent, AssetCopyService.class); - parent.startService(copyAssets); + if (PermissionsHandler.checkWritePermission(parent)) { + copyAssetsIfNeeded(parent); } Intent intent = parent.getIntent(); @@ -51,4 +45,16 @@ public final class StartupHandler } return false; } + + public static void copyAssetsIfNeeded(Activity parent) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(parent); + boolean assetsCopied = preferences.getBoolean("assetsCopied", false); + + if (!assetsCopied) + { + // Copy assets into appropriate locations. + Intent copyAssets = new Intent(parent, AssetCopyService.class); + parent.startService(copyAssets); + } + } } diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index d92e2a9398..f41d8fe8a2 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -238,4 +238,6 @@ org.dolphinemu.dolphinemu + + You need to allow write access to external storage for the emulator to work