Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Needs review] New "Profiles" functionality #919

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions app/src/main/java/com/limelight/AppView.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.StringReader;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService;
Expand Down Expand Up @@ -37,6 +39,7 @@
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AbsListView;
Expand All @@ -50,6 +53,7 @@
import org.xmlpull.v1.XmlPullParserException;

public class AppView extends Activity implements AdapterFragmentCallbacks {
private AdapterContextMenuInfo _contextMenuInfo = null;
private AppGridAdapter appGridAdapter;
private String uuidString;
private ShortcutHelper shortcutHelper;
Expand All @@ -70,6 +74,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
private final static int HIDE_APP_ID = 7;
private final static int PROFILE_GROUP = 1;

public final static String HIDDEN_APPS_PREF_FILENAME = "HiddenApps";

Expand Down Expand Up @@ -329,6 +334,29 @@ private void updateHiddenApps(boolean hideImmediately) {
appGridAdapter.updateHiddenApps(hiddenAppIds, hideImmediately);
}

private void updateAppProfile(String profileKey, Integer appId, boolean remove) {
SharedPreferences profConfig = getSharedPreferences(PreferenceConfiguration.APP_PROFILES_PREF_FILENAME, MODE_PRIVATE);
SharedPreferences.Editor edit = profConfig.edit();

// Go through every profile and remove this appId
Map<String,?> keys = profConfig.getAll();
for(Map.Entry<String,?> entry : keys.entrySet()){
if (entry.getValue() instanceof Set) {
Set<String> appIds = new HashSet<String>((HashSet<String>) entry.getValue());
appIds.remove(appId.toString());
edit.putStringSet(profileKey, appIds);
}
}

if (!remove) {
HashSet<String> profileAppIds = new HashSet<String>(profConfig.getStringSet(profileKey, new HashSet<String>()));
profileAppIds.add(appId.toString());
edit.putStringSet(profileKey, profileAppIds);
}

edit.apply();
}

private void populateAppGridWithCache() {
try {
// Try to load from cache
Expand Down Expand Up @@ -411,6 +439,24 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn

menu.add(Menu.NONE, VIEW_DETAILS_ID, 4, getResources().getString(R.string.applist_menu_details));

// Builds submenu on context menu with all profiles.
if (!PreferenceConfiguration.readPreferences(this).profileKeys.isEmpty()) {
SubMenu profileSubMenu = menu.addSubMenu(Menu.NONE, 7, 6, getResources().getString(R.string.applist_menu_app_profile));
Object[] array = PreferenceConfiguration.readPreferences(this).profileKeys.toArray();
for (int i = 0; i < array.length; i++) {
String s = array[i].toString();
MenuItem profileItem = profileSubMenu.add(PROFILE_GROUP, i, i, s);
String profileKey = PreferenceConfiguration.getAppProfileKey(selectedApp.app.getAppId(), this);

if (s.equals(profileKey)) {
profileItem.setChecked(true);
}
}

profileSubMenu.setGroupCheckable(PROFILE_GROUP, true, true);
}


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Only add an option to create shortcut if box art is loaded
// and when we're in grid-mode (not list-mode).
Expand All @@ -433,7 +479,31 @@ public void onContextMenuClosed(Menu menu) {
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();

SubMenu sMenu = item.getSubMenu();
// Android loses contextual menuInfo on context menu submenus, so this sets it manually
if (sMenu != null) {
_contextMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
return super.onContextItemSelected(item);
}
if (info == null && _contextMenuInfo != null) {
info = _contextMenuInfo;
}

final AppObject app = (AppObject) appGridAdapter.getItem(info.position);

if (item.getGroupId() == PROFILE_GROUP) {
if (item.isChecked()) {
// Removes profile
updateAppProfile(item.getTitle().toString(), app.app.getAppId(), true);
}
else {
// Sets profile
updateAppProfile(item.getTitle().toString(), app.app.getAppId(), false);
}
return false;
}

switch (item.getItemId()) {
case START_WITH_QUIT:
// Display a confirmation dialog first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
import android.os.Build;
import android.preference.PreferenceManager;

import com.limelight.Game;
import com.limelight.nvstream.StreamConfiguration;
import com.limelight.nvstream.jni.MoonBridge;

import java.util.Map;
import java.util.Set;

public class PreferenceConfiguration {
private static final String LEGACY_RES_FPS_PREF_STRING = "list_resolution_fps";
private static final String LEGACY_ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround";

static final String RESOLUTION_PREF_STRING = "list_resolution";
static final String FPS_PREF_STRING = "list_fps";
static final String RM_PROFILE_STRING = "remove_profile";
static final String BITRATE_PREF_STRING = "seekbar_bitrate_kbps";
public static final String APP_PROFILES_PREF_FILENAME = "AppProfiles";
private static final String BITRATE_PREF_OLD_STRING = "seekbar_bitrate";
private static final String STRETCH_PREF_STRING = "checkbox_stretch_video";
private static final String SOPS_PREF_STRING = "checkbox_enable_sops";
Expand Down Expand Up @@ -108,6 +115,7 @@ public class PreferenceConfiguration {
public boolean vibrateFallbackToDevice;
public boolean touchscreenTrackpad;
public MoonBridge.AudioConfiguration audioConfiguration;
public Set<String> profileKeys;

public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option
Expand Down Expand Up @@ -184,6 +192,19 @@ private static String getResolutionString(int width, int height) {
}
}

public static String getAppProfileKey(Integer appId, Context context) {

Map<String,?> keys = context.getSharedPreferences(APP_PROFILES_PREF_FILENAME, Context.MODE_PRIVATE).getAll();

for(Map.Entry<String,?> entry : keys.entrySet()){
Set value = (Set) entry.getValue();
if (value.contains(appId.toString())) {
return entry.getKey();
}
}
return null;
}

public static int getDefaultBitrate(String resString, String fpsString) {
int width = getWidthFromResolutionString(resString);
int height = getHeightFromResolutionString(resString);
Expand Down Expand Up @@ -280,7 +301,18 @@ public static void resetStreamingSettings(Context context) {
}

public static PreferenceConfiguration readPreferences(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences prefs;
String profileKey = null;
if (context instanceof Game) {
Integer appId = ((Game) context).getIntent().getIntExtra(Game.EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
profileKey = PreferenceConfiguration.getAppProfileKey(appId, context);
}
if (profileKey == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(context);
} else {
prefs = context.getSharedPreferences(profileKey, Context.MODE_PRIVATE);
}

PreferenceConfiguration config = new PreferenceConfiguration();

// Migrate legacy preferences to the new locations
Expand Down Expand Up @@ -417,6 +449,7 @@ else if (audioConfig.equals("51")) {
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
config.profileKeys = context.getSharedPreferences(APP_PROFILES_PREF_FILENAME, Context.MODE_PRIVATE).getAll().keySet();

return config;
}
Expand Down
90 changes: 90 additions & 0 deletions app/src/main/java/com/limelight/preferences/StreamSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.app.Activity;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
Expand All @@ -29,6 +30,9 @@
import com.limelight.utils.UiHelper;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
Expand Down Expand Up @@ -488,6 +492,92 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
return true;
}
});

final SharedPreferences profileMap = getActivity().getSharedPreferences(PreferenceConfiguration.APP_PROFILES_PREF_FILENAME, MODE_PRIVATE);
final Set<String> profileKeys = profileMap.getAll().keySet();
if (!profileKeys.isEmpty()) {
final ListPreference removeItem = (ListPreference) findPreference(PreferenceConfiguration.RM_PROFILE_STRING);
removeItem.setEnabled(true);

CharSequence[] entries = profileKeys.toArray(new CharSequence[0]);
removeItem.setEntries(entries);
removeItem.setEntryValues(entries);

removeItem.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String profileKey = (String) newValue;

// Deletes the profile map
profileMap.edit().remove(profileKey).apply();

// Deletes the profile configuration file
getActivity().getSharedPreferences(profileKey, MODE_PRIVATE)
.edit().clear().apply();

if (profileMap.getAll().isEmpty()) {
removeItem.setEnabled(false);
}

StreamSettings settingsActivity = (StreamSettings)SettingsFragment.this.getActivity();
if (settingsActivity != null) {
settingsActivity.reloadSettings();
}

// Don't actually update the value of the item.
return false;
}
});
}

EditTextPreference textPref = (EditTextPreference)findPreference("text_profile");
// Default profile text
if (textPref.getText() == null) {
textPref.setText("MyProfile");
}

textPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
String profileName = (String) newValue;
SharedPreferences profilePref = getActivity().getSharedPreferences(profileName, MODE_PRIVATE);
SharedPreferences.Editor ed = profilePref.edit();
ed.clear();
for (Map.Entry<String, ?> entry : prefs.getAll().entrySet()) {
Object value = entry.getValue();
String key = entry.getKey();
if (value instanceof String) {
ed.putString(key, ((String) value));
} else if (value instanceof Set) {
ed.putStringSet(key, (Set<String>) value);
} else if (value instanceof Integer) {
ed.putInt(key, (Integer) value);
} else if (value instanceof Long) {
ed.putLong(key, (Long) value);
} else if (value instanceof Float) {
ed.putFloat(key, (Float) value);
} else if (value instanceof Boolean) {
ed.putBoolean(key, (Boolean) value);
}
}

ed.apply();

// Save profile map as a separate config file
if (!profileMap.contains(profileName)) {
profileMap.edit().putStringSet(profileName, new HashSet<String>()).apply();
findPreference(PreferenceConfiguration.RM_PROFILE_STRING).setEnabled(true);
StreamSettings settingsActivity = (StreamSettings)SettingsFragment.this.getActivity();
if (settingsActivity != null) {
settingsActivity.reloadSettings();
}
}

return true;
}
});

}
}
}
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<string name="applist_menu_quit_and_start">Quit Current Game and Start</string>
<string name="applist_menu_cancel">Cancel</string>
<string name="applist_menu_details">View Details</string>
<string name="applist_menu_app_profile">Profile</string>
<string name="applist_menu_scut">Create Shortcut</string>
<string name="applist_menu_tv_channel">Add to Channel</string>
<string name="applist_menu_hide_app">Hide App</string>
Expand All @@ -138,6 +139,11 @@
<!-- Preferences -->
<string name="category_basic_settings">Basic Settings</string>
<string name="title_resolution_list">Video resolution</string>
<string name="profile_save_title">Save to profile…</string>
<string name="profile_save_description">Saves the current settings as a profile that can be assigned per game</string>
<string name="remove_profile_message">If you use the same name as another profile, it will be replaced</string>
<string name="remove_profile_title">Remove Profile</string>
<string name="remove_profile_description">Select the profile to remove</string>
<string name="summary_resolution_list">Increase to improve image clarity. Decrease for better performance on lower end devices and slower networks.</string>
<string name="title_native_res_dialog">Native Resolution Warning</string>
<string name="text_native_res_dialog">Native resolution modes are not officially supported by GeForce Experience, so it will not set your host display resolution itself. You will need to set it manually while in game.\n\nIf you choose to create a custom resolution in NVIDIA Control Panel to match your device resolution, please ensure you have read and understood NVIDIA\'s warning regarding possible monitor damage, PC instability, and other potential problems.\n\nWe are not responsible for any problems resulting from creating a custom resolution on your PC.\n\nFinally, your device or host PC may not support streaming at native resolution. If it doesn\'t work on your device, you\'re just out of luck unfortunately.</string>
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:seekbar="http://schemas.moonlight-stream.com/apk/res/seekbar">

<PreferenceCategory
android:key="category_profile"
android:title="Profile">

<EditTextPreference
android:dialogMessage="@string/remove_profile_message"
android:icon="@android:drawable/ic_menu_save"
android:inputType="textShortMessage|textNoSuggestions"
android:key="text_profile"
android:selectAllOnFocus="true"
android:singleLine="true"
android:summary="@string/profile_save_description"
android:title="@string/profile_save_title" />
<ListPreference
android:contentDescription="@string/remove_profile_description"
android:defaultValue="1"
android:enabled="false"
android:icon="@android:drawable/ic_menu_delete"
android:key="remove_profile"
android:summary="@string/remove_profile_description"
android:title="@string/remove_profile_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_basic_settings"
android:key="category_basic_settings">
<ListPreference
Expand Down