/* Copyright 2015 Braden Farmer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.farmerbb.secondscreen.activity; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.app.NotificationManager; import android.app.ProgressDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.appcompat.app.AppCompatActivity; import android.view.Window; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.farmerbb.secondscreen.BuildConfig; import com.farmerbb.secondscreen.R; import com.farmerbb.secondscreen.fragment.ProfileEditFragment; import com.farmerbb.secondscreen.fragment.ProfileListFragment; import com.farmerbb.secondscreen.fragment.ProfileViewFragment; import com.farmerbb.secondscreen.fragment.WelcomeFragment; import com.farmerbb.secondscreen.fragment.dialog.AboutDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.AndroidUpgradeDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.BusyboxDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.DeleteDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.ExpertModeSizeDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.FirstLoadDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.FirstRunDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.MultipleVersionsDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.NewDeviceDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.NewProfileDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.ReloadProfileDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.SystemAlertPermissionDialogFragment; import com.farmerbb.secondscreen.fragment.dialog.UiRefreshDialogFragment; import com.farmerbb.secondscreen.receiver.BootReceiver; import com.farmerbb.secondscreen.receiver.PackageUpgradeReceiver; import com.farmerbb.secondscreen.receiver.TaskerActionReceiver; import com.farmerbb.secondscreen.receiver.TaskerConditionReceiver; import com.farmerbb.secondscreen.service.DisplayConnectionService; import com.farmerbb.secondscreen.service.NotificationService; import com.farmerbb.secondscreen.util.U; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Scanner; // This is the main activity of the application. This is the activity that is launched when // SecondScreen is selected in the user's application launcher. // It shows fragments in a single-pane layout on phones and a dual-pane layout on tablets and larger // screens. The left pane will always show the ProfileListFragment. The right pane will show, // depending on where the user is in the application flow, either the WelcomeFragment, // ProfileViewFragment, or ProfileEditFragment. // The single-pane layout follows the flow of the dual-pane layout's right pane, with // ProfileListFragment shown in place of WelcomeFragment (except initially on the first run). // // The MainActivity also takes care of some basic application initialization, such as running a // safeguard check, testing for superuser access, setting some initial preferences, gathering basic // info about the user's device, launching services, and displaying the warning dialog on first run. public final class MainActivity extends AppCompatActivity implements FirstRunDialogFragment.Listener, FirstLoadDialogFragment.Listener, NewProfileDialogFragment.Listener, DeleteDialogFragment.Listener, ReloadProfileDialogFragment.Listener, ExpertModeSizeDialogFragment.Listener, AndroidUpgradeDialogFragment.Listener, NewDeviceDialogFragment.Listener, UiRefreshDialogFragment.Listener, MultipleVersionsDialogFragment.Listener, BusyboxDialogFragment.Listener, ProfileListFragment.Listener, ProfileEditFragment.Listener, ProfileViewFragment.Listener, AboutDialogFragment.Listener, SystemAlertPermissionDialogFragment.Listener { private final class RunSafeguard extends AsyncTask<Void, Void, Void> { private ProgressDialog dialog; @Override protected void onPreExecute() { dialog = new ProgressDialog(MainActivity.this); dialog.setMessage(getResources().getString(R.string.checking_for_superuser)); dialog.setIndeterminate(true); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { // Build commands to pass to su final int chromeCommand = 0; final int densityCommand = 1; final int densityCommand2 = 2; final int sizeCommand = 3; final int overscanCommand = 4; final int immersiveCommand = 5; final int freeformCommand = 6; final int hdmiRotationCommand = 7; // Initialize su array String[] su = new String[hdmiRotationCommand + 1]; Arrays.fill(su, ""); su[chromeCommand] = U.chromeCommandRemove; su[sizeCommand] = U.sizeCommand(MainActivity.this, "reset"); if(!(getPackageManager().hasSystemFeature("com.cyanogenmod.android") && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1)) { su[densityCommand] = U.densityCommand(MainActivity.this, "reset"); // We run the density command twice, for reliability su[densityCommand2] = su[densityCommand]; } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) su[overscanCommand] = U.overscanCommand(MainActivity.this, "reset"); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) su[immersiveCommand] = U.immersiveCommand("do-nothing"); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && U.getTaskbarPackageName(MainActivity.this) == null) su[freeformCommand] = U.freeformCommand(false); su[hdmiRotationCommand] = U.hdmiRotationCommand + "landscape"; // Run superuser commands U.runCommands(MainActivity.this, su, false); return null; } @Override protected void onPostExecute(Void result) { try { dialog.dismiss(); // Show welcome dialog if this is the user's first time running SecondScreen if(getFragmentManager().findFragmentByTag("firstrunfragment") == null) { DialogFragment firstRun = new FirstRunDialogFragment(); firstRun.show(getFragmentManager(), "firstrunfragment"); } } catch (IllegalStateException e) { finish(); } catch (IllegalArgumentException e) { /* Gracefully fail */ } } } boolean showBusyboxDialog = true; boolean showUpgradeDialog = true; int clicks = 0; boolean returningFromGrantingSystemAlertPermission = false; String savedFilename; private BroadcastReceiver showDialogsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { showMoreDialogs(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState != null) { showBusyboxDialog = savedInstanceState.getBoolean("show-busybox-dialog"); showUpgradeDialog = savedInstanceState.getBoolean("show-upgrade-dialog"); } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Remove margins from layout on Lollipop devices LinearLayout layout = findViewById(R.id.profileViewEdit); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) layout.getLayoutParams(); params.setMargins(0, 0, 0, 0); layout.setLayoutParams(params); // Set action bar elevation getSupportActionBar().setElevation(getResources().getDimensionPixelSize(R.dimen.action_bar_elevation)); } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { getWindow().setDecorCaptionShade(Window.DECOR_CAPTION_SHADE_DARK); } LocalBroadcastManager.getInstance(this).registerReceiver(showDialogsReceiver, new IntentFilter("com.farmerbb.secondscreen.SHOW_DIALOGS")); // Handle cases where both the free and (formerly) paid versions may be installed at the same time PackageInfo paidPackage; PackageInfo freePackage; try { paidPackage = getPackageManager().getPackageInfo("com.farmerbb.secondscreen", 0); freePackage = getPackageManager().getPackageInfo("com.farmerbb.secondscreen.free", 0); Bundle bundle = new Bundle(); if(paidPackage.firstInstallTime > freePackage.firstInstallTime) bundle.putString("package", paidPackage.packageName); else bundle.putString("package", freePackage.packageName); if(getFragmentManager().findFragmentByTag("multiple-versions-fragment") == null) { DialogFragment multiple = new MultipleVersionsDialogFragment(); multiple.setArguments(bundle); multiple.show(getFragmentManager(), "multiple-versions-fragment"); } } catch (PackageManager.NameNotFoundException e) { // Hooray! Only one version of the app is installed, so proceed with app launch. SharedPreferences prefMain = U.getPrefMain(this); // Ensure that all receivers are enabled and stay enabled, so that critical functionality // associated with these receivers can always run when needed initializeComponents(BootReceiver.class.getName()); initializeComponents(PackageUpgradeReceiver.class.getName()); initializeComponents(TaskerActionReceiver.class.getName()); initializeComponents(TaskerConditionReceiver.class.getName()); // Previous free versions of SecondScreen disabled certain activities containing // functionality that, at the time, was restricted to the paid version of SecondScreen. // Ensure that these activities are always enabled for users on 2.0+ initializeComponents(HdmiActivity.class.getName()); initializeComponents(HdmiProfileSelectActivity.class.getName()); initializeComponents(QuickLaunchActivity.class.getName()); initializeComponents(TaskerConditionActivity.class.getName()); initializeComponents(TaskerQuickActionsActivity.class.getName()); // Restore DisplayConnectionService if(prefMain.getBoolean("hdmi", true) && prefMain.getBoolean("first-run", false)) { Intent serviceIntent = new Intent(this, DisplayConnectionService.class); U.startService(this, serviceIntent); } // Restore NotificationService SharedPreferences prefCurrent = U.getPrefCurrent(this); if(!prefCurrent.getBoolean("not_active", true)) { Intent serviceIntent = new Intent(this, NotificationService.class); U.startService(this, serviceIntent); } if(prefMain.getBoolean("first-run", false)) { // Clear preferences from ProfileEditFragment, just in case if(!(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment)) { SharedPreferences prefNew = U.getPrefNew(this); SharedPreferences.Editor prefNewEditor = prefNew.edit(); prefNewEditor.clear(); prefNewEditor.apply(); } // Save preferences when upgrading to 2.0+ from a previous free version if("null".equals(prefMain.getString("notification_action", "null"))) { SharedPreferences.Editor editor = prefMain.edit(); editor.putString("notification_action", "quick-actions"); editor.apply(); } // Determine if we need to show any dialogs before we create the fragments if(savedInstanceState == null) showDialogs(); // Read debug mode preference if(isDebugModeEnabled(false)) clicks = 10; // Set launcher shortcuts on API 25+ setLauncherShortcuts(); // Finally, create fragments createFragments(); } else if(getFragmentManager().findFragmentByTag("firstrunfragment") == null) // This is our first time running SecondScreen. // Run the safeguard commands before doing anything else (new RunSafeguard()).execute(); } } @Override protected void onResume() { super.onResume(); if(returningFromGrantingSystemAlertPermission) { returningFromGrantingSystemAlertPermission = false; String filename = savedFilename; savedFilename = null; onFirstLoadPositiveClick(null, filename, false); } } @Override protected void onStart() { super.onStart(); // Set MainActivity active state SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences.Editor editor = prefMain.edit(); editor.putBoolean("inactive", false); editor.apply(); } @Override protected void onStop() { super.onStop(); // Set MainActivity active state SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences.Editor editor = prefMain.edit(); editor.remove("inactive"); editor.apply(); } @Override protected void onDestroy() { LocalBroadcastManager.getInstance(this).unregisterReceiver(showDialogsReceiver); super.onDestroy(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean("show-busybox-dialog", showBusyboxDialog); savedInstanceState.putBoolean("show-upgrade-dialog", showUpgradeDialog); super.onSaveInstanceState(savedInstanceState); } private void setLauncherShortcuts() { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); if(shortcutManager.getDynamicShortcuts().size() == 0) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(BuildConfig.APPLICATION_ID, TaskerQuickActionsActivity.class.getName()); intent.putExtra("launched-from-app", true); ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "quick_actions") .setShortLabel(getString(R.string.label_quick_actions)) .setIcon(Icon.createWithResource(this, R.drawable.shortcut_icon)) .setIntent(intent) .build(); shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut)); } } } private void createFragments() { // Begin a new FragmentTransaction FragmentTransaction transaction = getFragmentManager().beginTransaction(); // This fragment shows ProfileListFragment as a sidebar (only seen in tablet mode landscape) if(!(getFragmentManager().findFragmentById(R.id.profileList) instanceof ProfileListFragment)) transaction.replace(R.id.profileList, new ProfileListFragment(), "ProfileListFragment"); // This fragment shows ProfileListFragment in the main screen area (only seen on phones and tablet mode portrait), // but only if it doesn't already contain ProfileViewFragment or ProfileEditFragment. // If ProfileListFragment is already showing in the sidebar, use WelcomeFragment instead if(!((getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) || (getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileViewFragment))) { SharedPreferences prefMain = U.getPrefMain(this); if(prefMain.getBoolean("show-welcome-message", false) || (getFragmentManager().findFragmentById(R.id.profileViewEdit) == null && findViewById(R.id.layoutMain).getTag().equals("main-layout-large")) || ((getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileListFragment) && findViewById(R.id.layoutMain).getTag().equals("main-layout-large"))) { // Show welcome message Bundle bundle = new Bundle(); bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false)); Fragment fragment = new WelcomeFragment(); fragment.setArguments(bundle); transaction.replace(R.id.profileViewEdit, fragment, "NoteListFragment"); } else if(findViewById(R.id.layoutMain).getTag().equals("main-layout-normal")) transaction.replace(R.id.profileViewEdit, new ProfileListFragment(), "NoteListFragment"); } // Commit fragment transaction transaction.commit(); } private void initializeComponents(String name) { ComponentName component = new ComponentName(getPackageName(), name); getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } @Override public void onBackPressed() { if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.onBackPressed(fragment.getFilename(), false, true); } else if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileViewFragment) { ProfileViewFragment fragment = (ProfileViewFragment) getFragmentManager().findFragmentByTag("ProfileViewFragment"); fragment.onBackPressed(); } else finish(); } @Override public void onFirstLoadPositiveClick(DialogFragment dialog, String filename, boolean isChecked) { SharedPreferences prefMain = U.getPrefMain(this); // Set checkbox preference if(isChecked) { SharedPreferences.Editor firstLoadEditor = prefMain.edit(); firstLoadEditor.putBoolean("first-load", true); firstLoadEditor.apply(); } // Dismiss dialog if(dialog != null) dialog.dismiss(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this) && !"do-nothing".equals(U.getPrefSaved(this, filename).getString("rotation_lock_new", "fallback")) && !prefMain.getBoolean("dont_show_system_alert_dialog", false)) { returningFromGrantingSystemAlertPermission = true; savedFilename = filename; DialogFragment fragment = new SystemAlertPermissionDialogFragment(); fragment.show(getFragmentManager(), "SystemAlertPermissionDialogFragment"); } else { U.loadProfile(this, filename); // Add ProfileListFragment or WelcomeFragment Fragment fragment; if(findViewById(R.id.layoutMain).getTag().equals("main-layout-normal")) fragment = new ProfileListFragment(); else { Bundle bundle = new Bundle(); bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false)); fragment = new WelcomeFragment(); fragment.setArguments(bundle); } getFragmentManager() .beginTransaction() .replace(R.id.profileViewEdit, fragment, "ProfileListFragment") .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) .commit(); } } @Override public void onFirstRunDialogNegativeClick() { uninstallPackage(getPackageName()); } @SuppressLint("HardwareIds") @Override public void onFirstRunDialogPositiveClick() { SharedPreferences prefMain = U.getPrefMain(this); // Check if "first-run" preference hasn't already been set if(!prefMain.getBoolean("first-run", false)) { // Initialize preferences U.initPrefs(this); // Restore DisplayConnectionService Intent serviceIntent = new Intent(this, DisplayConnectionService.class); U.startService(this, serviceIntent); // Determine if we need to show any dialogs before we create the fragments showDialogs(); // Set launcher shortcuts on API 25+ setLauncherShortcuts(); // Finally, create fragments createFragments(); } } public void onLoadProfileButtonClick(String filename) { SharedPreferences prefMain = U.getPrefMain(this); if(prefMain.getBoolean("first-load", false)) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this) && "landscape".equals(U.getPrefSaved(this, filename).getString("rotation_lock_new", "fallback")) && !prefMain.getBoolean("dont_show_system_alert_dialog", false)) { returningFromGrantingSystemAlertPermission = true; savedFilename = filename; DialogFragment fragment = new SystemAlertPermissionDialogFragment(); fragment.show(getFragmentManager(), "SystemAlertPermissionDialogFragment"); } else { U.loadProfile(this, filename); // Add ProfileListFragment or WelcomeFragment Fragment fragment; if(findViewById(R.id.layoutMain).getTag().equals("main-layout-normal")) fragment = new ProfileListFragment(); else { Bundle bundle = new Bundle(); bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false)); fragment = new WelcomeFragment(); fragment.setArguments(bundle); } getFragmentManager() .beginTransaction() .replace(R.id.profileViewEdit, fragment, "ProfileListFragment") .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) .commit(); } } else { Bundle bundle = new Bundle(); bundle.putString("filename", filename); DialogFragment firstLoadFragment = new FirstLoadDialogFragment(); firstLoadFragment.setArguments(bundle); firstLoadFragment.show(getFragmentManager(), "first-load"); } } @Override public void onTurnOffProfileButtonClick() { // User touched the dialog's positive button try { SharedPreferences prefCurrent = U.getPrefCurrent(this); if(!prefCurrent.getBoolean("not_active", true)) U.turnOffProfile(this); // Add ProfileListFragment or WelcomeFragment Fragment fragment; if(findViewById(R.id.layoutMain).getTag().equals("main-layout-normal")) fragment = new ProfileListFragment(); else { SharedPreferences prefMain = U.getPrefMain(this); Bundle bundle = new Bundle(); bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false)); fragment = new WelcomeFragment(); fragment.setArguments(bundle); } getFragmentManager() .beginTransaction() .replace(R.id.profileViewEdit, fragment, "ProfileListFragment") .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) .commit(); } catch (NullPointerException e) { /* Gracefully fail */ } } @Override public void onDeleteDialogPositiveClick(DialogFragment dialog) { // User touched the dialog's positive button // Dismiss dialog dialog.dismiss(); if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.deleteProfile(); } else if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileViewFragment) { ProfileViewFragment fragment = (ProfileViewFragment) getFragmentManager().findFragmentByTag("ProfileViewFragment"); fragment.deleteProfile(); } } @Override public void editProfile(String filename) { String currentFilename; boolean isEditing = false; if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); currentFilename = fragment.getFilename(); isEditing = true; } else if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileViewFragment) { ProfileViewFragment fragment = (ProfileViewFragment) getFragmentManager().findFragmentByTag("ProfileViewFragment"); currentFilename = fragment.getFilename(); } else currentFilename = " "; if(currentFilename.equals(filename)) { if(!isEditing) switchFragments(filename, true); } else switchFragments(filename, true); } @Override public void viewProfile(String filename) { String currentFilename; boolean isViewing = false; if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); currentFilename = fragment.getFilename(); } else if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileViewFragment) { ProfileViewFragment fragment = (ProfileViewFragment) getFragmentManager().findFragmentByTag("ProfileViewFragment"); currentFilename = fragment.getFilename(); isViewing = true; } else currentFilename = " "; if(currentFilename.equals(filename)) { if(!isViewing) switchFragments(filename, false); } else switchFragments(filename, false); } private void switchFragments(String filename, boolean type) { if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment editFragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); editFragment.onBackPressed(filename, type, false); } else { Fragment fragment; String tag; if(type) { fragment = new ProfileEditFragment(); tag = "ProfileEditFragment"; } else { fragment = new ProfileViewFragment(); tag = "ProfileViewFragment"; } Bundle bundle = new Bundle(); bundle.putString("filename", filename); try { bundle.putString("title", U.getProfileTitle(this, filename)); } catch (IOException e) { /* Gracefully fail */ } fragment.setArguments(bundle); // Add fragment getFragmentManager() .beginTransaction() .replace(R.id.profileViewEdit, fragment, tag) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .commit(); } } @Override public String getProfileTitle(String filename) throws IOException { return U.getProfileTitle(this, filename); } @Override public void showDeleteDialog() { DialogFragment deleteFragment = new DeleteDialogFragment(); deleteFragment.show(getFragmentManager(), "delete"); } @Override public void showExpertModeDialog() { SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences prefNew = U.getPrefNew(this); String size = prefNew.getString("size", "reset"); int height; int width; if("reset".equals(size)) { height = prefMain.getInt("height", 0); width = prefMain.getInt("width", 0); } else { Scanner scanner = new Scanner(size); scanner.useDelimiter("x"); width = scanner.nextInt(); height = scanner.nextInt(); scanner.close(); } Bundle bundle = new Bundle(); if(prefMain.getBoolean("landscape", false)) { bundle.putString("height", Integer.toString(width)); bundle.putString("width", Integer.toString(height)); } else { bundle.putString("height", Integer.toString(height)); bundle.putString("width", Integer.toString(width)); } DialogFragment sizeFragment = new ExpertModeSizeDialogFragment(); sizeFragment.setArguments(bundle); sizeFragment.show(getFragmentManager(), "expert-mode-size"); } @Override public void showReloadDialog(String filename, boolean isEdit, boolean returnToList) { Bundle bundle = new Bundle(); bundle.putString("filename", filename); bundle.putBoolean("is-edit", isEdit); bundle.putBoolean("return-to-list", returnToList); DialogFragment reloadProfileFragment = new ReloadProfileDialogFragment(); reloadProfileFragment.setArguments(bundle); reloadProfileFragment.show(getFragmentManager(), "reload"); } @Override public void onReloadDialogPositiveClick(String filename, boolean isEdit, boolean returnToList) { if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.onReloadDialogPositiveClick(filename, isEdit, returnToList); } } @Override public void onReloadDialogNegativeClick(String filename, boolean isEdit, boolean returnToList) { if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.onReloadDialogNegativeClick(filename, isEdit, returnToList); } } @Override public SharedPreferences getPrefCurrent() { return U.getPrefCurrent(this); } @Override public SharedPreferences getPrefQuickActions() { return U.getPrefQuickActions(this); } @Override public TextView getHelperText() { return findViewById(R.id.textView1); } @Override public void onNewProfilePositiveClick(String name, int pos) { U.createProfileFromTemplate(this, name, pos, U.getPrefNew(this)); // Add ProfileEditFragment getFragmentManager() .beginTransaction() .replace(R.id.profileViewEdit, new ProfileEditFragment(), "ProfileEditFragment") .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .commit(); } @Override public void onExpertModeSizePositiveClick(String height, String width) { SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences prefNew = U.getPrefNew(this); SharedPreferences.Editor editor = prefNew.edit(); if(prefMain.getBoolean("landscape", false)) { if(height.isEmpty()) height = Integer.toString(prefMain.getInt("width", 0)); if(width.isEmpty()) width = Integer.toString(prefMain.getInt("height", 0)); editor.putString("size", height + "x" + width); } else { if(height.isEmpty()) height = Integer.toString(prefMain.getInt("height", 0)); if(width.isEmpty()) width = Integer.toString(prefMain.getInt("width", 0)); editor.putString("size", width + "x" + height); } editor.apply(); ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); U.bindPreferenceSummaryToValue(fragment.findPreference("size"), ProfileEditFragment.opcl); } @Override public void setDefaultDensity() { SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences prefNew = U.getPrefNew(this); SharedPreferences.Editor editor = prefNew.edit(); String density = Integer.toString(U.getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0))); editor.putString("density", density); editor.apply(); ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); U.bindPreferenceSummaryToValue(fragment.findPreference("density"), ProfileEditFragment.opcl); } @Override public String generateBlurb(String key, String value) { return U.generateBlurb(this, key, value, true); } @Override public void setEmptyTitle(String title) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) setTitle(title); else setTitle(" " + title); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == 42 && resultCode == RESULT_OK && (getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment)) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.setPrefChange(data.getBooleanExtra("pref-change", false)); } } @Override public void onUpgradeDialogPositiveClick(DialogFragment dialog) { dialog.dismiss(); SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences.Editor editor = prefMain.edit(); editor.putFloat("current_api_version_new", U.getCurrentApiVersion()); editor.apply(); } @Override public void onUpgradeDialogNegativeClick(DialogFragment dialog) { dialog.dismiss(); U.checkForUpdates(this); SharedPreferences prefMain = U.getPrefMain(this); SharedPreferences.Editor editor = prefMain.edit(); editor.putFloat("current_api_version_new", U.getCurrentApiVersion()); editor.apply(); } @Override public void onNewDeviceDialogPositiveClick(DialogFragment dialog) { dialog.dismiss(); showMoreDialogs(); } @Override public void showUiRefreshDialog(String filename, boolean isEdit, boolean returnToList) { Bundle bundle = new Bundle(); bundle.putString("filename", filename); bundle.putBoolean("is-edit", isEdit); bundle.putBoolean("return-to-list", returnToList); DialogFragment uiRefreshFragment = new UiRefreshDialogFragment(); uiRefreshFragment.setArguments(bundle); uiRefreshFragment.show(getFragmentManager(), "ui-refresh"); } @Override public void onUiRefreshDialogPositiveClick(String filename, boolean isEdit, boolean returnToList) { if(getFragmentManager().findFragmentById(R.id.profileViewEdit) instanceof ProfileEditFragment) { ProfileEditFragment fragment = (ProfileEditFragment) getFragmentManager().findFragmentByTag("ProfileEditFragment"); fragment.preSave(filename, isEdit, returnToList); } } @Override public void uninstallPackage(String packageName) { Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, Uri.parse("package:" + packageName)); try { startActivity(uninstallIntent); } catch (ActivityNotFoundException e) { /* Gracefully fail */ } finish(); } private void showDialogs() { if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || U.hasElevatedPermissions(this)) showMoreDialogs(); else startActivity(new Intent(this, UnableToStartActivity.class)); } private void showMoreDialogs() { new Handler().postDelayed(this::reallyShowMoreDialogs, 100); } @SuppressLint("HardwareIds") private void reallyShowMoreDialogs() { SharedPreferences prefMain = U.getPrefMain(this); // Save Android ID to preferences, and show dialog if ID does not match (new device) if("null".equals(prefMain.getString("android_id", "null"))) { SharedPreferences.Editor editor = prefMain.edit(); editor.putString("android_id", Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); editor.apply(); } else if(!Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID).equals(prefMain.getString("android_id", "null"))) { SharedPreferences.Editor editor = prefMain.edit(); editor.putString("android_id", Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); editor.apply(); if(getFragmentManager().findFragmentByTag("new-device-fragment") == null) { DialogFragment newDeviceFragment = new NewDeviceDialogFragment(); newDeviceFragment.show(getFragmentManager(), "new-device-fragment"); } // Show dialog if on an untested Android version } else if(U.isUntestedAndroidVersion(this) && getFragmentManager().findFragmentByTag("upgrade-fragment") == null && showUpgradeDialog) { showUpgradeDialog = false; DialogFragment upgradeFragment = new AndroidUpgradeDialogFragment(); upgradeFragment.show(getFragmentManager(), "upgrade-fragment"); // Starting with 5.1.1 LMY48I, RunningAppProcessInfo no longer returns valid data, // which means we won't be able to use the "kill" command with the pid of SystemUI. // On Marshmallow, this isn't an issue, as we have the "pkill" command available as part // of toybox. Users on recent builds of 5.1.1, however, will need to have busybox // installed to run "pkill". // // Thus, if the SystemUI pid gets returned as 0, we need to check for either busybox or // toybox on the device, and inform the user if either of these are missing. } else if(getFragmentManager().findFragmentByTag("busybox-fragment") == null && showBusyboxDialog) { showBusyboxDialog = false; ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> pids = am.getRunningAppProcesses(); int processid = 0; // Get SystemUI pid for(ActivityManager.RunningAppProcessInfo process : pids) { if(process.processName.equalsIgnoreCase("com.android.systemui")) processid = process.pid; } if(processid == 0) { File toybox = new File("/system/bin", "toybox"); File busybox = new File("/system/xbin", "busybox"); File busybox2 = new File("/system/bin", "busybox"); if(!(toybox.exists() || busybox.exists() || busybox2.exists()) && !prefMain.getBoolean("ignore_busybox_dialog", false)) { DialogFragment busyboxFragment = new BusyboxDialogFragment(); busyboxFragment.show(getFragmentManager(), "busybox-fragment"); } } } } @Override public void onBusyboxDialogNegativeClick() { U.getPrefMain(this).edit().putBoolean("ignore_busybox_dialog", true).apply(); } // Enables and disables debug mode. Debug mode can be enabled by tapping on the helper text // 10 times, and is used for simulating actions on non-rooted devices, among other things. @Override public boolean isDebugModeEnabled(boolean isClick) { SharedPreferences prefMain = U.getPrefMain(this); if(isClick && U.getPrefCurrent(this).getBoolean("not_active", true)) { clicks++; U.cancelToast(); if(clicks > 5 && clicks < 10) { String message = String.format(getResources().getString(R.string.debug_mode_enabling), 10 - clicks); showDebugModeToast(message); } else if(clicks >= 10) { SharedPreferences.Editor editor = prefMain.edit(); if(prefMain.getBoolean("debug_mode", false)) { editor.putBoolean("debug_mode", false); showDebugModeToast(getString(R.string.debug_mode_disabled)); // Clean up leftover notifications NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.cancelAll(); // Clean up leftover dump files File file = new File(getExternalFilesDir(null), "prefCurrent.xml"); File file2 = new File(getExternalFilesDir(null), "prefSaved.xml"); File file3 = new File(getExternalFilesDir(null), "prefMain.xml"); File file4 = new File(getExternalFilesDir(null), "prefNew.xml"); file.delete(); file2.delete(); file3.delete(); file4.delete(); } else { editor.putBoolean("debug_mode", true); showDebugModeToast(getString(R.string.debug_mode_enabled)); } editor.apply(); } } return prefMain.getBoolean("debug_mode", false); } private void showDebugModeToast(final String message) { new Handler().postDelayed(() -> U.showToast(this, message, Toast.LENGTH_SHORT), 100); } @Override public void onAboutDialogNegativeClick(DialogFragment dialog) { dialog.dismiss(); U.checkForUpdates(this); } @TargetApi(Build.VERSION_CODES.M) @Override public void onSystemAlertPermissionDialogPositiveClick() { try { startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + BuildConfig.APPLICATION_ID))); } catch (ActivityNotFoundException e) { U.showErrorDialog(this, "SYSTEM_ALERT_WINDOW"); } } @Override public void onSystemAlertPermissionDialogNegativeClick() { U.getPrefMain(this).edit().putBoolean("dont_show_system_alert_dialog", true).apply(); if(returningFromGrantingSystemAlertPermission) { returningFromGrantingSystemAlertPermission = false; String filename = savedFilename; savedFilename = null; onFirstLoadPositiveClick(null, filename, false); } } }