Refactored code

This commit is contained in:
SSimco
2024-10-29 21:40:53 +02:00
parent bdd6b3f835
commit afe29d5b1d
98 changed files with 1176 additions and 908 deletions
+7 -3
View File
@@ -32,6 +32,7 @@ jobs:
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
if: "${{ env.ANDROID_STORE_FILE_BASE64 != '' }}"
run: |
echo "BUILD_TYPE=Release" >> "$GITHUB_ENV"
cd ./src/android
base64 --decode <<< "${ANDROID_STORE_FILE_BASE64}" > store.jks
ANDROID_STORE_FILE=$(pwd)/store.jks
@@ -39,13 +40,16 @@ jobs:
- name: Build Cemu for Android
run: |
if [[ -z "$BUILD_TYPE" ]]; then
BUILD_TYPE=Debug
fi
cd ./src/android
./gradlew assembleRelease
./gradlew assemble$BUILD_TYPE
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cemu-android
path: |
./src/android/app/build/outputs/apk/release/*.apk
./src/android/app/build/outputs/apk/release/*.aab
./src/android/app/build/outputs/apk/*/*.apk
./src/android/app/build/outputs/apk/*/*.aab
+2
View File
@@ -19,6 +19,7 @@ android {
versionName "1.0"
ndk {
// abiFilters("x86_64", "arm64-v8a")
//noinspection ChromeOsAbiSupport
abiFilters("arm64-v8a")
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -41,6 +42,7 @@ android {
if (releaseStoreFile != null) {
signingConfig signingConfigs.release
} else {
resValue("string", "app_name", "Cemu debug")
signingConfig signingConfigs.debug
}
}
+1 -2
View File
@@ -57,8 +57,7 @@
android:parentActivityName=".MainActivity" />
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Cemu">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -181,13 +181,15 @@ Java_info_cemu_Cemu_nativeinterface_NativeEmulation_initializeEmulation([[maybe_
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeEmulation_initializerRenderer(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject testSurface)
Java_info_cemu_Cemu_nativeinterface_NativeEmulation_initializeRenderer(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject j_testSurface)
{
JNIUtils::handleNativeException(env, [&]() {
cemu_assert_debug(testSurface != nullptr);
// TODO: cleanup surface
GuiSystem::getWindowInfo().window_main.surface = ANativeWindow_fromSurface(env, testSurface);
cemu_assert_debug(j_testSurface != nullptr);
ANativeWindow* testSurface = ANativeWindow_fromSurface(env, j_testSurface);
GuiSystem::getWindowInfo().window_main.surface = testSurface;
g_renderer = std::make_unique<VulkanRenderer>();
GuiSystem::getWindowInfo().window_main.surface = nullptr;
ANativeWindow_release(testSurface);
});
}
@@ -100,7 +100,7 @@ extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeGraphicPacks_getGraphicPackBasicInfos(JNIEnv* env, [[maybe_unused]] jclass clazz)
{
auto graphicPackInfoClass = env->FindClass("info/cemu/Cemu/nativeinterface/NativeGraphicPacks$GraphicPackBasicInfo");
auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "<init>", "(JLjava/lang/String;Ljava/util/ArrayList;)V");
auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "<init>", "(JLjava/lang/String;ZLjava/util/ArrayList;)V");
std::vector<jobject> graphicPackInfoJObjects;
for (auto&& graphicPack : NativeGraphicPacks::s_graphicPacks)
@@ -108,7 +108,7 @@ Java_info_cemu_Cemu_nativeinterface_NativeGraphicPacks_getGraphicPackBasicInfos(
jstring virtualPath = env->NewStringUTF(graphicPack.second->GetVirtualPath().c_str());
jlong id = graphicPack.first;
jobject titleIds = JNIUtils::createJavaLongArrayList(env, graphicPack.second->GetTitleIds());
jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath, titleIds);
jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath, graphicPack.second->IsEnabled(), titleIds);
graphicPackInfoJObjects.push_back(jGraphicPack);
}
return JNIUtils::createArrayList(env, graphicPackInfoJObjects);
@@ -26,8 +26,9 @@ public class CemuApplication extends Application {
public File getInternalFolder() {
var externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null)
if (externalFilesDir != null) {
return externalFilesDir;
}
return getFilesDir();
}
@@ -7,7 +7,7 @@ import android.graphics.drawable.Drawable;
import java.util.Objects;
public class DrawableExtensions {
public class Drawables {
private static final ColorMatrix INVERTED_COLOR_MATRIX = new ColorMatrix(
new float[]{
-1, 0, 0, 0, 255,
@@ -18,14 +18,12 @@ public class DrawableExtensions {
);
public static Drawable getInvertedDrawable(Drawable drawable, Resources resources) {
var newDrawable = Objects.requireNonNull(drawable.getConstantState())
.newDrawable(resources);
var newDrawable = Objects.requireNonNull(drawable.getConstantState()).newDrawable(resources);
return applyInvertedColorTransform(newDrawable);
}
public static Drawable applyInvertedColorTransform(Drawable drawable) {
drawable = drawable.mutate();
drawable.setColorFilter(new ColorMatrixColorFilter(INVERTED_COLOR_MATRIX));
drawable.mutate().setColorFilter(new ColorMatrixColorFilter(INVERTED_COLOR_MATRIX));
return drawable;
}
}
@@ -14,8 +14,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -27,7 +25,7 @@ import info.cemu.Cemu.databinding.ActivityEmulationBinding;
import info.cemu.Cemu.input.InputManager;
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
public class EmulationActivity extends AppCompatActivity implements Observer<EmulationData> {
public class EmulationActivity extends AppCompatActivity {
private boolean hasEmulationError;
public static final String EXTRA_LAUNCH_PATH = BuildConfig.APPLICATION_ID + ".LaunchPath";
private final InputManager inputManager = new InputManager();
@@ -39,8 +37,9 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
*/
@SuppressWarnings("unused")
public static void showEmulationTextInput(String initialText, int maxLength) {
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog != null)
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog != null) {
return;
}
NativeSwkbd.setCurrentInputText(initialText);
emulationActivityInstance.runOnUiThread(() -> {
var inputEditTextLayout = emulationActivityInstance.getLayoutInflater().inflate(R.layout.layout_emulation_input, null);
@@ -55,8 +54,9 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
doneButton.setEnabled(false);
doneButton.setOnClickListener(v -> inputEditText.onFinishedEdit());
inputEditText.setOnTextChangedListener(s -> doneButton.setEnabled(s.length() > 0));
if (maxLength > 0)
if (maxLength > 0) {
inputEditText.appendFilter(new InputFilter.LengthFilter(maxLength));
}
emulationActivityInstance.emulationTextInputDialog = dialog;
});
}
@@ -66,8 +66,9 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
*/
@SuppressWarnings("unused")
public static void hideEmulationTextInput() {
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog == null)
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog == null) {
return;
}
var textInputDialog = emulationActivityInstance.emulationTextInputDialog;
emulationActivityInstance.emulationTextInputDialog = null;
emulationActivityInstance.runOnUiThread(textInputDialog::dismiss);
@@ -75,15 +76,17 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (inputManager.onMotionEvent(event))
if (inputManager.onMotionEvent(event)) {
return true;
}
return super.onGenericMotionEvent(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (inputManager.onKeyEvent(event))
if (inputManager.onKeyEvent(event)) {
return true;
}
return super.dispatchKeyEvent(event);
}
@@ -98,8 +101,6 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
}
});
EmulationViewModel viewModel = new ViewModelProvider(this).get(EmulationViewModel.class);
viewModel.getEmulationData().observe(this, this);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
Uri data = intent.getData();
@@ -114,11 +115,12 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
throw new RuntimeException("launchPath is null");
}
setFullscreen();
info.cemu.Cemu.databinding.ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
if (emulationFragment == null) {
emulationFragment = new EmulationFragment(launchPath);
emulationFragment.setOnEmulationErrorCallback(this::onEmulationError);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.emulation_frame, emulationFragment)
@@ -133,17 +135,19 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
.setMessage(R.string.exit_confirm_message)
.setPositiveButton(R.string.yes, (dialog, which) -> quit())
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.create()
.show();
}
private void onEmulationError(EmulationError emulationError) {
private void onEmulationError(String emulationError) {
if (hasEmulationError) {
return;
}
hasEmulationError = true;
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.error)
.setMessage(emulationError.errorMessage())
.setMessage(emulationError)
.setNeutralButton(R.string.quit, (dialog, which) -> dialog.dismiss())
.setOnDismissListener(dialog -> quit())
.create()
.show();
}
@@ -158,12 +162,4 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
finishAffinity();
System.exit(0);
}
@Override
public void onChanged(EmulationData emulationData) {
if (emulationData.emulationError().isPresent() && !hasEmulationError) {
hasEmulationError = true;
onEmulationError(emulationData.emulationError().get());
}
}
}
@@ -1,6 +0,0 @@
package info.cemu.Cemu.emulation;
import java.util.Optional;
public record EmulationData(Optional<EmulationError> emulationError) {
}
@@ -1,4 +0,0 @@
package info.cemu.Cemu.emulation;
public record EmulationError(String errorMessage) {
}
@@ -20,14 +20,13 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.PopupMenu;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import info.cemu.Cemu.nativeinterface.NativeEmulation;
import info.cemu.Cemu.R;
import info.cemu.Cemu.databinding.FragmentEmulationBinding;
import info.cemu.Cemu.input.SensorManager;
import info.cemu.Cemu.inputoverlay.InputOverlaySettingsProvider;
import info.cemu.Cemu.inputoverlay.InputOverlaySurfaceView;
import info.cemu.Cemu.nativeinterface.NativeEmulation;
import info.cemu.Cemu.nativeinterface.NativeException;
import info.cemu.Cemu.nativeinterface.NativeInput;
@@ -102,43 +101,48 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
}
}
public interface OnEmulationErrorCallback {
void onEmulationError(String errorMessage);
}
private final String launchPath;
private boolean isGameRunning;
private SurfaceView padCanvas;
private SurfaceTexture testSurfaceTexture;
private Surface testSurface;
private Toast toast;
private FragmentEmulationBinding binding;
private boolean isMotionEnabled;
private PopupMenu settingsMenu;
private InputOverlaySurfaceView inputOverlaySurfaceView;
private SensorManager sensorManager;
private EmulationViewModel viewModel;
private OnEmulationErrorCallback onEmulationErrorCallback;
private boolean hasEmulationError;
private InputOverlaySettingsProvider.OverlaySettings overlaySettings;
public EmulationFragment(String launchPath) {
this.launchPath = launchPath;
}
InputOverlaySettingsProvider.OverlaySettings overlaySettings;
public void setOnEmulationErrorCallback(OnEmulationErrorCallback onEmulationErrorCallback) {
this.onEmulationErrorCallback = onEmulationErrorCallback;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
var inputOverlaySettingsProvider = new InputOverlaySettingsProvider(requireContext());
if (sensorManager == null)
if (sensorManager == null) {
sensorManager = new SensorManager(requireContext());
}
sensorManager.setIsLandscape(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
overlaySettings = inputOverlaySettingsProvider.getOverlaySettings();
testSurfaceTexture = new SurfaceTexture(0);
testSurface = new Surface(testSurfaceTexture);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (sensorManager != null)
if (sensorManager != null) {
sensorManager.setIsLandscape(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
}
@Override
@@ -150,21 +154,23 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
@Override
public void onResume() {
super.onResume();
if (isMotionEnabled)
if (isMotionEnabled) {
sensorManager.startListening();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (sensorManager != null)
if (sensorManager != null) {
sensorManager.pauseListening();
if (testSurface != null) testSurface.release();
if (testSurfaceTexture != null) testSurfaceTexture.release();
}
}
private void createPadCanvas() {
if (padCanvas != null) return;
if (padCanvas != null) {
return;
}
padCanvas = new SurfaceView(requireContext());
binding.canvasesLayout.addView(
padCanvas,
@@ -183,7 +189,9 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
}
private void destroyPadCanvas() {
if (padCanvas == null) return;
if (padCanvas == null) {
return;
}
binding.canvasesLayout.removeView(padCanvas);
padCanvas = null;
}
@@ -223,10 +231,11 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
}
if (itemId == R.id.enable_motion) {
isMotionEnabled = !item.isChecked();
if (isMotionEnabled)
if (isMotionEnabled) {
sensorManager.startListening();
else
} else {
sensorManager.pauseListening();
}
item.setChecked(isMotionEnabled);
return true;
}
@@ -248,22 +257,22 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(requireActivity()).get(EmulationViewModel.class);
binding = FragmentEmulationBinding.inflate(inflater, container, false);
inputOverlaySurfaceView = binding.inputOverlay;
binding.moveInputsButton.setOnClickListener(v -> {
if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_POSITION)
if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_POSITION) {
return;
}
binding.resizeInputsButton.setAlpha(0.5f);
binding.moveInputsButton.setAlpha(1.0f);
toastMessage(R.string.input_mode_edit_position);
inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_POSITION);
});
binding.resizeInputsButton.setOnClickListener(v -> {
if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_SIZE)
if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_SIZE) {
return;
}
binding.moveInputsButton.setAlpha(0.5f);
binding.resizeInputsButton.setAlpha(1.0f);
toastMessage(R.string.input_mode_edit_size);
@@ -288,7 +297,11 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
}
SurfaceView mainCanvas = binding.mainCanvas;
try {
NativeEmulation.initializerRenderer(testSurface);
SurfaceTexture testSurfaceTexture = new SurfaceTexture(0);
Surface testSurface = new Surface(testSurfaceTexture);
NativeEmulation.initializeRenderer(testSurface);
testSurface.release();
testSurfaceTexture.release();
} catch (NativeException exception) {
onEmulationError(getString(R.string.failed_initialize_renderer_error, exception.getMessage()));
return binding.getRoot();
@@ -303,8 +316,9 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
if (hasEmulationError)
if (hasEmulationError) {
return;
}
if (!isGameRunning) {
isGameRunning = true;
startGame();
@@ -321,22 +335,22 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
private void startGame() {
int result = NativeEmulation.startGame(launchPath);
if (result == NativeEmulation.START_GAME_SUCCESSFUL)
if (result == NativeEmulation.START_GAME_SUCCESSFUL) {
return;
int errorMessageId = switch (result) {
case NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND ->
R.string.game_not_found;
case NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> R.string.no_disk_key;
case NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> R.string.no_title_tik;
default -> R.string.game_files_unknown_error;
}
String errorMessage = switch (result) {
case NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND -> getString(R.string.game_not_found);
case NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> getString(R.string.no_disk_key);
case NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> getString(R.string.no_title_tik);
default -> getString(R.string.game_files_unknown_error, launchPath);
};
onEmulationError(getString(errorMessageId));
}
private void onEmulationError(String errorMessage) {
hasEmulationError = true;
if (viewModel == null)
return;
viewModel.setEmulationError(new EmulationError(errorMessage));
if (onEmulationErrorCallback != null) {
onEmulationErrorCallback.onEmulationError(errorMessage);
}
}
}
@@ -35,18 +35,21 @@ public class EmulationTextInputEditText extends TextInputEditText {
public void updateText(String text) {
boolean hasFocus = hasFocus();
if (hasFocus)
if (hasFocus) {
clearFocus();
}
setText(text);
if (hasFocus)
if (hasFocus) {
requestFocus();
}
}
public EmulationTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
appendFilter((source, start, end, dest, dstart, dend) -> {
if (INPUT_PATTERN.matcher(source).matches())
if (INPUT_PATTERN.matcher(source).matches()) {
return null;
}
return "";
});
setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_NORMAL);
@@ -57,10 +60,12 @@ public class EmulationTextInputEditText extends TextInputEditText {
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
if (!hasFocus())
if (!hasFocus()) {
return;
if (onTextChangedListener != null)
}
if (onTextChangedListener != null) {
onTextChangedListener.onTextChanged(text);
}
NativeSwkbd.onTextChanged(text.toString());
}
@@ -1,19 +0,0 @@
package info.cemu.Cemu.emulation;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.Optional;
public class EmulationViewModel extends ViewModel {
private final MutableLiveData<EmulationData> emulationData = new MutableLiveData<>(new EmulationData(Optional.empty()));
public void setEmulationError(EmulationError emulationError) {
emulationData.setValue(new EmulationData(Optional.of(emulationError)));
}
public LiveData<EmulationData> getEmulationData() {
return emulationData;
}
}
@@ -85,7 +85,9 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
if (parentDocumentId == null || documentId == null) return false;
if (parentDocumentId == null || documentId == null) {
return false;
}
return documentId.startsWith(parentDocumentId);
}
@@ -95,12 +97,14 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
var newFile = resolveWithoutConflict(parentFile, displayName);
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
if (!newFile.mkdir())
if (!newFile.mkdir()) {
throw new FileNotFoundException("Failed to create directory");
}
} else {
try {
if (!newFile.createNewFile())
if (!newFile.createNewFile()) {
throw new FileNotFoundException("Failed to create file");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -116,25 +120,31 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
deleteFolder(file);
return;
}
if (!file.delete())
if (!file.delete()) {
throw new FileNotFoundException("Couldn't delete document with ID " + documentId);
}
}
private void deleteFolder(File dirFile) throws FileNotFoundException {
if (!dirFile.isDirectory())
if (!dirFile.isDirectory()) {
return;
}
var files = dirFile.listFiles();
if (files == null) return;
if (files == null) {
return;
}
for (var file : files) {
if (file.isDirectory()) {
deleteFolder(file);
continue;
}
if (!file.delete())
if (!file.delete()) {
throw new FileNotFoundException("Couldn't delete file " + file.getPath());
}
}
if (!dirFile.delete())
if (!dirFile.delete()) {
throw new FileNotFoundException("Couldn't delete file " + dirFile.getPath());
}
}
@Override
@@ -142,30 +152,35 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
var parent = getFile(parentDocumentId);
var file = getFile(documentId);
if (!(parent.equals(file) || file.getParentFile() == null || file.getParentFile().equals(parent)))
if (!(parent.equals(file) || file.getParentFile() == null || file.getParentFile().equals(parent))) {
throw new FileNotFoundException("Couldn't delete document with ID " + documentId);
}
if (file.isDirectory()) {
deleteFolder(file);
return;
}
if (!file.delete())
if (!file.delete()) {
throw new FileNotFoundException("Couldn't delete document with ID " + documentId);
}
}
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
if (displayName == null)
if (displayName == null) {
throw new FileNotFoundException("Couldn't rename document " + documentId + " as the new name is null");
}
var sourceFile = getFile(documentId);
var sourceParentFile = sourceFile.getParentFile();
if (sourceParentFile == null)
if (sourceParentFile == null) {
throw new FileNotFoundException("Couldn't rename document '" + documentId + "' as it has no parent");
}
var destFile = resolve(sourceParentFile, displayName);
try {
if (!sourceFile.renameTo(destFile))
if (!sourceFile.renameTo(destFile)) {
throw new FileNotFoundException("Couldn't rename document from '" + sourceFile.getName() + "' to '" + destFile.getName() + "'");
}
} catch (Exception exception) {
throw new FileNotFoundException("Couldn't rename document from '" + sourceFile.getName() + "' to '" + destFile.getName() + "':" + exception.getMessage());
}
@@ -180,8 +195,9 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
var newFile = resolveWithoutConflict(parent, oldFile.getName());
try {
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) {
throw new IOException("Couldn't create new file");
}
try (var inputStream = new FileInputStream(oldFile); var outputStream = new FileOutputStream(newFile)) {
byte[] b = new byte[1024];
int len;
@@ -211,10 +227,12 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
var cursor = new MatrixCursor(projection == null ? DEFAULT_DOCUMENT_PROJECTION : projection);
var parent = getFile(parentDocumentId);
var files = parent.listFiles();
if (files == null)
if (files == null) {
return cursor;
for (var file : files)
}
for (var file : files) {
includeFile(cursor, null, file);
}
return cursor;
}
@@ -230,15 +248,17 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
}
private String copyDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
throw new FileNotFoundException("Couldn't copy document '" + sourceDocumentId + "' as its parent is not '" + sourceParentDocumentId + "'");
}
return copyDocument(sourceDocumentId, targetParentDocumentId);
}
private File resolveWithoutConflict(File originalFile, String name) {
var file = resolve(originalFile, name);
if (!file.exists())
if (!file.exists()) {
return file;
}
// Makes sure two files don't have the same name by adding a number to the end
var noConflictId = 1;
@@ -276,13 +296,15 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
.add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
.add(DocumentsContract.Document.COLUMN_FLAGS, flags);
if (localFile.equals(baseDirectory))
if (localFile.equals(baseDirectory)) {
curorRowBuilder.add(DocumentsContract.Root.COLUMN_ICON, R.mipmap.ic_launcher);
}
}
private String getTypeForFile(File file) {
if (file.isDirectory())
if (file.isDirectory()) {
return DocumentsContract.Document.MIME_TYPE_DIR;
}
return getTypeForName(file.getName());
}
@@ -291,8 +313,9 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
if (lastDot >= 0) {
var extension = name.substring(lastDot + 1);
var mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null)
if (mime != null) {
return mime;
}
}
return "application/octect-stream";
}
@@ -301,8 +324,9 @@ public class DocumentsProvider extends android.provider.DocumentsProvider {
Objects.requireNonNull(documentId);
if (documentId.startsWith(ROOT_ID)) {
var file = resolve(baseDirectory, documentId.substring(ROOT_ID.length() + 1));
if (!file.exists())
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath() + " " + documentId + " not found");
}
return file;
} else {
throw new FileNotFoundException(documentId + " is not in any known root");
@@ -79,7 +79,9 @@ public class GameAdapter extends ListAdapter<Game, GameAdapter.ViewHolder> {
@Override
public void onBindViewHolder(@NonNull GameAdapter.ViewHolder holder, int position) {
Game game = getItem(position);
if (game == null) return;
if (game == null) {
return;
}
holder.icon.setImageBitmap(game.icon());
holder.favoriteIcon.setVisibility(game.isFavorite() ? View.VISIBLE : View.GONE);
holder.text.setText(game.name());
@@ -23,7 +23,7 @@ import info.cemu.Cemu.nativeinterface.NativeGameTitles;
import info.cemu.Cemu.nativeinterface.NativeGameTitles.Game;
public class GameDetailsFragment extends Fragment {
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -31,10 +31,12 @@ public class GameDetailsFragment extends Fragment {
var game = new ViewModelProvider(requireActivity()).get(GameViewModel.class).getGame();
binding.gameTitleName.setText(game.name());
binding.titleVersion.setText(String.valueOf(game.version()));
if (game.icon() != null)
if (game.icon() != null) {
binding.titleIcon.setImageBitmap(game.icon());
if (game.dlc() != 0)
}
if (game.dlc() != 0) {
binding.titleDlc.setText(String.valueOf(game.dlc()));
}
binding.titleTimePlayed.setText(getTimePlayed(game));
binding.titleLastPlayed.setText(getLastPlayedDate(game));
binding.titleId.setText(String.format("%016x", game.titleId()));
@@ -44,15 +46,20 @@ public class GameDetailsFragment extends Fragment {
}
private String getLastPlayedDate(Game game) {
if (game.lastPlayedYear() == 0) return getString(R.string.never_played);
if (game.lastPlayedYear() == 0) {
return getString(R.string.never_played);
}
LocalDate lastPlayedDate = LocalDate.of(game.lastPlayedYear(), game.lastPlayedMonth(), game.lastPlayedDay());
return dateFormatter.format(lastPlayedDate);
return DATE_FORMATTER.format(lastPlayedDate);
}
private String getTimePlayed(Game game) {
if (game.minutesPlayed() == 0) return getString(R.string.never_played);
if (game.minutesPlayed() < 60)
if (game.minutesPlayed() == 0) {
return getString(R.string.never_played);
}
if (game.minutesPlayed() < 60) {
return getString(R.string.minutes_played, game.minutesPlayed());
}
return getString(R.string.hours_minutes_played, game.minutesPlayed() / 60, game.minutesPlayed() % 60);
}
@@ -23,6 +23,9 @@ public class GameListViewModel extends ViewModel {
public GameListViewModel() {
this.gamesData = new MutableLiveData<>();
NativeGameTitles.setGameTitleLoadedCallback(game -> {
if (!isGameValid(game)) {
return;
}
synchronized (GameListViewModel.this) {
games.add(game);
gamesData.postValue(new ArrayList<>(games));
@@ -30,9 +33,15 @@ public class GameListViewModel extends ViewModel {
});
}
private boolean isGameValid(Game game) {
return game.path() != null && game.name() != null;
}
public void setGameTitleFavorite(Game game, boolean isFavorite) {
synchronized (this) {
if (!games.contains(game)) return;
if (!games.contains(game)) {
return;
}
NativeGameTitles.setGameTitleFavorite(game.titleId(), isFavorite);
games.remove(game);
Game newGame = new Game(
@@ -28,12 +28,9 @@ public class GameProfileEditFragment extends Fragment {
private String cpuModeToString(int cpuMode) {
int resourceId = switch (cpuMode) {
case NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER ->
R.string.cpu_mode_single_core_interpreter;
case NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER ->
R.string.cpu_mode_single_core_recompiler;
case NativeGameTitles.CPU_MODE_MULTICORERECOMPILER ->
R.string.cpu_mode_multi_core_recompiler;
case NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER -> R.string.cpu_mode_single_core_interpreter;
case NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER -> R.string.cpu_mode_single_core_recompiler;
case NativeGameTitles.CPU_MODE_MULTICORERECOMPILER -> R.string.cpu_mode_multi_core_recompiler;
default -> R.string.cpu_mode_auto;
};
return getString(resourceId);
@@ -1,7 +1,11 @@
package info.cemu.Cemu.gameview;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -21,6 +25,7 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.HashSet;
@@ -47,7 +52,7 @@ public class GamesFragment extends Fragment {
super.onCreate(savedInstanceState);
currentGamePaths = new HashSet<>(NativeSettings.getGamesPaths());
gameAdapter = new GameAdapter(game -> {
Intent intent = new Intent(getContext(), EmulationActivity.class);
Intent intent = new Intent(requireContext(), EmulationActivity.class);
intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path());
startActivity(intent);
});
@@ -74,8 +79,9 @@ public class GamesFragment extends Fragment {
MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.game, menu);
Game selectedGame = gameAdapter.getSelectedGame();
if (selectedGame == null)
if (selectedGame == null) {
return;
}
menu.findItem(R.id.favorite).setChecked(selectedGame.isFavorite());
menu.findItem(R.id.remove_shader_caches).setEnabled(NativeGameTitles.titleHasShaderCacheFiles(selectedGame.titleId()));
}
@@ -105,9 +111,36 @@ public class GamesFragment extends Fragment {
NavHostFragment.findNavController(this).navigate(R.id.action_games_fragment_to_game_details_fragment);
return true;
}
if (itemId == R.id.create_shortcut) {
createShortcutForGame(game);
return true;
}
return super.onContextItemSelected(item);
}
private void createShortcutForGame(Game game) {
var context = requireContext();
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
if (!shortcutManager.isRequestPinShortcutSupported()) {
Toast.makeText(context, R.string.shortcut_not_supported, Toast.LENGTH_SHORT).show();
return;
}
if (game.icon() == null) {
return;
}
Intent intent = new Intent(requireContext(), EmulationActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path());
ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, String.valueOf(game.titleId()))
.setShortLabel(game.name())
.setIntent(intent)
.setIcon(Icon.createWithBitmap(game.icon()))
.build();
Intent pinnedShortcutCallbackIntent = shortcutManager.createShortcutResultIntent(pinShortcutInfo);
PendingIntent successCallback = PendingIntent.getBroadcast(context, 0, pinnedShortcutCallbackIntent, PendingIntent.FLAG_IMMUTABLE);
shortcutManager.requestPinShortcut(pinShortcutInfo, successCallback.getIntentSender());
}
private void removeShaderCachesForGame(Game game) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
builder.setTitle(R.string.remove_shader_caches)
@@ -153,7 +186,9 @@ public class GamesFragment extends Fragment {
});
binding.gamesSwipeRefresh.setOnRefreshListener(() -> {
if (refreshing) return;
if (refreshing) {
return;
}
refreshing = true;
handler.postDelayed(() -> {
binding.gamesSwipeRefresh.setRefreshing(false);
@@ -161,6 +196,8 @@ public class GamesFragment extends Fragment {
}, 1000);
gameListViewModel.refreshGames();
});
binding.gamesSwipeRefresh.setColorSchemeColors(MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorOnSurfaceVariant, Color.BLACK));
binding.gamesSwipeRefresh.setProgressBackgroundColorSchemeColor(MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorSurfaceVariant, Color.WHITE));
recyclerView.setAdapter(gameAdapter);
return binding.getRoot();
@@ -34,8 +34,9 @@ public abstract class BaseSelectionAdapter<T> extends BaseAdapter {
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
if (view == null)
if (view == null) {
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_single_selection_item, viewGroup, false);
}
MaterialRadioButton radioButton = view.findViewById(R.id.single_selection_item_radio_button);
radioButton.setEnabled(isEnabled(position));
setRadioButtonText(radioButton, position);

Some files were not shown because too many files have changed in this diff Show More