mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-01 23:35:28 +00:00
503 lines
18 KiB
Java
503 lines
18 KiB
Java
package com.limelight;
|
|
|
|
import java.io.StringReader;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.UUID;
|
|
|
|
import com.limelight.computers.ComputerManagerListener;
|
|
import com.limelight.computers.ComputerManagerService;
|
|
import com.limelight.grid.AppGridAdapter;
|
|
import com.limelight.nvstream.http.ComputerDetails;
|
|
import com.limelight.nvstream.http.NvApp;
|
|
import com.limelight.nvstream.http.NvHTTP;
|
|
import com.limelight.preferences.PreferenceConfiguration;
|
|
import com.limelight.ui.AdapterFragment;
|
|
import com.limelight.ui.AdapterFragmentCallbacks;
|
|
import com.limelight.utils.CacheHelper;
|
|
import com.limelight.utils.Dialog;
|
|
import com.limelight.utils.ServerHelper;
|
|
import com.limelight.utils.SpinnerDialog;
|
|
import com.limelight.utils.UiHelper;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Service;
|
|
import android.content.ComponentName;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.res.Configuration;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.view.ContextMenu;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ContextMenu.ContextMenuInfo;
|
|
import android.widget.AbsListView;
|
|
import android.widget.AdapterView;
|
|
import android.widget.AdapterView.OnItemClickListener;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
|
|
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|
private AppGridAdapter appGridAdapter;
|
|
private String uuidString;
|
|
|
|
private ComputerDetails computer;
|
|
private ComputerManagerService.ApplistPoller poller;
|
|
private SpinnerDialog blockingLoadSpinner;
|
|
private String lastRawApplist;
|
|
private int lastRunningAppId;
|
|
private boolean suspendGridUpdates;
|
|
|
|
private final static int START_OR_RESUME_ID = 1;
|
|
private final static int QUIT_ID = 2;
|
|
private final static int CANCEL_ID = 3;
|
|
private final static int START_WTIH_QUIT = 4;
|
|
|
|
public final static String NAME_EXTRA = "Name";
|
|
public final static String UUID_EXTRA = "UUID";
|
|
|
|
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
|
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
|
public void onServiceConnected(ComponentName className, IBinder binder) {
|
|
final ComputerManagerService.ComputerManagerBinder localBinder =
|
|
((ComputerManagerService.ComputerManagerBinder)binder);
|
|
|
|
// Wait in a separate thread to avoid stalling the UI
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
// Wait for the binder to be ready
|
|
localBinder.waitForReady();
|
|
|
|
// Now make the binder visible
|
|
managerBinder = localBinder;
|
|
|
|
// Get the computer object
|
|
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
|
|
|
try {
|
|
appGridAdapter = new AppGridAdapter(AppView.this,
|
|
PreferenceConfiguration.readPreferences(AppView.this).listMode,
|
|
PreferenceConfiguration.readPreferences(AppView.this).smallIconMode,
|
|
computer, managerBinder.getUniqueId());
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
// Start updates
|
|
startComputerUpdates();
|
|
|
|
// Load the app grid with cached data (if possible)
|
|
populateAppGridWithCache();
|
|
|
|
getFragmentManager().beginTransaction()
|
|
.replace(R.id.appFragmentContainer, new AdapterFragment())
|
|
.commitAllowingStateLoss();
|
|
}
|
|
}.start();
|
|
}
|
|
|
|
public void onServiceDisconnected(ComponentName className) {
|
|
managerBinder = null;
|
|
}
|
|
};
|
|
|
|
private void startComputerUpdates() {
|
|
if (managerBinder == null) {
|
|
return;
|
|
}
|
|
|
|
managerBinder.startPolling(new ComputerManagerListener() {
|
|
@Override
|
|
public void notifyComputerUpdated(ComputerDetails details) {
|
|
// Do nothing if updates are suspended
|
|
if (suspendGridUpdates) {
|
|
return;
|
|
}
|
|
|
|
// Don't care about other computers
|
|
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
|
|
return;
|
|
}
|
|
|
|
if (details.state == ComputerDetails.State.OFFLINE) {
|
|
// The PC is unreachable now
|
|
AppView.this.runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Display a toast to the user and quit the activity
|
|
Toast.makeText(AppView.this, getResources().getText(R.string.lost_connection), Toast.LENGTH_SHORT).show();
|
|
finish();
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// App list is the same or empty
|
|
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
|
|
|
|
// Let's check if the running app ID changed
|
|
if (details.runningGameId != lastRunningAppId) {
|
|
// Update the currently running game using the app ID
|
|
lastRunningAppId = details.runningGameId;
|
|
updateUiWithServerinfo(details);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
lastRunningAppId = details.runningGameId;
|
|
lastRawApplist = details.rawAppList;
|
|
|
|
try {
|
|
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
|
|
|
|
if (blockingLoadSpinner != null) {
|
|
blockingLoadSpinner.dismiss();
|
|
blockingLoadSpinner = null;
|
|
}
|
|
} catch (Exception ignored) {}
|
|
}
|
|
});
|
|
|
|
if (poller == null) {
|
|
poller = managerBinder.createAppListPoller(computer);
|
|
}
|
|
poller.start();
|
|
}
|
|
|
|
private void stopComputerUpdates() {
|
|
if (poller != null) {
|
|
poller.stop();
|
|
}
|
|
|
|
if (managerBinder != null) {
|
|
managerBinder.stopPolling();
|
|
}
|
|
|
|
if (appGridAdapter != null) {
|
|
appGridAdapter.cancelQueuedOperations();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
String locale = PreferenceConfiguration.readPreferences(this).language;
|
|
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
Configuration config = new Configuration(getResources().getConfiguration());
|
|
config.locale = new Locale(locale);
|
|
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
}
|
|
|
|
setContentView(R.layout.activity_app_view);
|
|
|
|
UiHelper.notifyNewRootView(this);
|
|
|
|
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
|
|
|
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
|
TextView label = (TextView) findViewById(R.id.appListText);
|
|
setTitle(labelText);
|
|
label.setText(labelText);
|
|
|
|
// Bind to the computer manager service
|
|
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
|
Service.BIND_AUTO_CREATE);
|
|
}
|
|
|
|
private void populateAppGridWithCache() {
|
|
try {
|
|
// Try to load from cache
|
|
lastRawApplist = CacheHelper.readInputStreamToString(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString));
|
|
List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
|
|
updateUiWithAppList(applist);
|
|
LimeLog.info("Loaded applist from cache");
|
|
} catch (Exception e) {
|
|
if (lastRawApplist != null) {
|
|
LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
|
|
e.printStackTrace();
|
|
}
|
|
LimeLog.info("Loading applist from the network");
|
|
// We'll need to load from the network
|
|
loadAppsBlocking();
|
|
}
|
|
}
|
|
|
|
private void loadAppsBlocking() {
|
|
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
|
|
getResources().getString(R.string.applist_refresh_msg), true);
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
super.onDestroy();
|
|
|
|
SpinnerDialog.closeDialogs(this);
|
|
Dialog.closeDialogs();
|
|
|
|
if (managerBinder != null) {
|
|
unbindService(serviceConnection);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
|
|
startComputerUpdates();
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
|
|
stopComputerUpdates();
|
|
}
|
|
|
|
private int getRunningAppId() {
|
|
int runningAppId = -1;
|
|
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
|
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
|
if (app.app.getIsRunning()) {
|
|
runningAppId = app.app.getAppId();
|
|
break;
|
|
}
|
|
}
|
|
return runningAppId;
|
|
}
|
|
|
|
@Override
|
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
super.onCreateContextMenu(menu, v, menuInfo);
|
|
|
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
|
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
|
int runningAppId = getRunningAppId();
|
|
if (runningAppId != -1) {
|
|
if (runningAppId == selectedApp.app.getAppId()) {
|
|
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
|
|
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
|
}
|
|
else {
|
|
menu.add(Menu.NONE, START_WTIH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
|
|
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onContextMenuClosed(Menu menu) {
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
|
switch (item.getItemId()) {
|
|
case START_WTIH_QUIT:
|
|
// Display a confirmation dialog first
|
|
UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
|
}
|
|
}, null);
|
|
return true;
|
|
|
|
case START_OR_RESUME_ID:
|
|
// Resume is the same as start for us
|
|
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
|
return true;
|
|
|
|
case QUIT_ID:
|
|
// Display a confirmation dialog first
|
|
UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
suspendGridUpdates = true;
|
|
ServerHelper.doQuit(AppView.this,
|
|
ServerHelper.getCurrentAddressFromComputer(computer),
|
|
app.app, managerBinder, new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Trigger a poll immediately
|
|
suspendGridUpdates = false;
|
|
if (poller != null) {
|
|
poller.pollNow();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, null);
|
|
return true;
|
|
|
|
case CANCEL_ID:
|
|
return true;
|
|
|
|
default:
|
|
return super.onContextItemSelected(item);
|
|
}
|
|
}
|
|
|
|
private void updateUiWithServerinfo(final ComputerDetails details) {
|
|
AppView.this.runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
boolean updated = false;
|
|
|
|
// Look through our current app list to tag the running app
|
|
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
|
|
|
// There can only be one or zero apps running.
|
|
if (existingApp.app.getIsRunning() &&
|
|
existingApp.app.getAppId() == details.runningGameId) {
|
|
// This app was running and still is, so we're done now
|
|
return;
|
|
}
|
|
else if (existingApp.app.getAppId() == details.runningGameId) {
|
|
// This app wasn't running but now is
|
|
existingApp.app.setIsRunning(true);
|
|
updated = true;
|
|
}
|
|
else if (existingApp.app.getIsRunning()) {
|
|
// This app was running but now isn't
|
|
existingApp.app.setIsRunning(false);
|
|
updated = true;
|
|
}
|
|
else {
|
|
// This app wasn't running and still isn't
|
|
}
|
|
}
|
|
|
|
if (updated) {
|
|
appGridAdapter.notifyDataSetChanged();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void updateUiWithAppList(final List<NvApp> appList) {
|
|
AppView.this.runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
boolean updated = false;
|
|
|
|
// First handle app updates and additions
|
|
for (NvApp app : appList) {
|
|
boolean foundExistingApp = false;
|
|
|
|
// Try to update an existing app in the list first
|
|
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
|
if (existingApp.app.getAppId() == app.getAppId()) {
|
|
// Found the app; update its properties
|
|
if (existingApp.app.getIsRunning() != app.getIsRunning()) {
|
|
existingApp.app.setIsRunning(app.getIsRunning());
|
|
updated = true;
|
|
}
|
|
if (!existingApp.app.getAppName().equals(app.getAppName())) {
|
|
existingApp.app.setAppName(app.getAppName());
|
|
updated = true;
|
|
}
|
|
|
|
foundExistingApp = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundExistingApp) {
|
|
// This app must be new
|
|
appGridAdapter.addApp(new AppObject(app));
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
// Next handle app removals
|
|
int i = 0;
|
|
while (i < appGridAdapter.getCount()) {
|
|
boolean foundExistingApp = false;
|
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
|
|
|
// Check if this app is in the latest list
|
|
for (NvApp app : appList) {
|
|
if (existingApp.app.getAppId() == app.getAppId()) {
|
|
foundExistingApp = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This app was removed in the latest app list
|
|
if (!foundExistingApp) {
|
|
appGridAdapter.removeApp(existingApp);
|
|
updated = true;
|
|
|
|
// Check this same index again because the item at i+1 is now at i after
|
|
// the removal
|
|
continue;
|
|
}
|
|
|
|
// Move on to the next item
|
|
i++;
|
|
}
|
|
|
|
if (updated) {
|
|
appGridAdapter.notifyDataSetChanged();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public int getAdapterFragmentLayoutId() {
|
|
return PreferenceConfiguration.readPreferences(this).listMode ?
|
|
R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
|
R.layout.app_grid_view_small : R.layout.app_grid_view);
|
|
}
|
|
|
|
@Override
|
|
public void receiveAbsListView(AbsListView listView) {
|
|
listView.setAdapter(appGridAdapter);
|
|
listView.setOnItemClickListener(new OnItemClickListener() {
|
|
@Override
|
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
|
long id) {
|
|
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
|
|
|
// Only open the context menu if something is running, otherwise start it
|
|
if (getRunningAppId() != -1) {
|
|
openContextMenu(arg1);
|
|
} else {
|
|
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
|
}
|
|
}
|
|
});
|
|
registerForContextMenu(listView);
|
|
listView.requestFocus();
|
|
}
|
|
|
|
public class AppObject {
|
|
public final NvApp app;
|
|
|
|
public AppObject(NvApp app) {
|
|
if (app == null) {
|
|
throw new IllegalArgumentException("app must not be null");
|
|
}
|
|
this.app = app;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return app.getAppName();
|
|
}
|
|
}
|
|
}
|