Create the PC channel on pairing and add each app to it upon launch

This commit is contained in:
Cameron Gutman 2019-07-12 00:23:13 -07:00
parent d9c0830198
commit fd53122cb3
6 changed files with 163 additions and 177 deletions

View File

@ -19,7 +19,6 @@ import com.limelight.utils.Dialog;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.TvChannelHelper;
import com.limelight.utils.UiHelper;
import android.app.Activity;
@ -51,7 +50,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter;
private String uuidString;
private ShortcutHelper shortcutHelper;
private TvChannelHelper tvChannelHelper;
private ComputerDetails computer;
private ComputerManagerService.ApplistPoller poller;
@ -67,10 +65,10 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private final static int START_WITH_QUIT = 4;
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
private final static int ADD_TO_TV_CHANNEL = 7;
public final static String NAME_EXTRA = "Name";
public final static String UUID_EXTRA = "UUID";
public final static String NEW_PAIR_EXTRA = "NewPair";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@ -187,9 +185,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_not_paired));
//Delete channel of PC for now
tvChannelHelper.deleteChannel(details.uuid);
// Display a toast to the user and quit the activity
Toast.makeText(AppView.this, getResources().getText(R.string.scut_not_paired), Toast.LENGTH_SHORT).show();
finish();
@ -258,7 +253,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
inForeground = true;
shortcutHelper = new ShortcutHelper(this);
tvChannelHelper = new TvChannelHelper(this);
UiHelper.setLocale(this);
@ -275,9 +269,8 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
label.setText(computerName);
// Add a launcher shortcut for this PC (forced, since this is user interaction)
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true, getIntent().getBooleanExtra(NEW_PAIR_EXTRA, false));
shortcutHelper.reportShortcutUsed(uuidString);
tvChannelHelper.createTvChannel(uuidString, computerName);
// Bind to the computer manager service
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
@ -356,9 +349,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
if (tvChannelHelper.isSupport()) {
menu.add(Menu.NONE, ADD_TO_TV_CHANNEL, 4, getResources().getString(R.string.applist_menu_tv_channel));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
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).
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
@ -434,10 +425,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}
return true;
case ADD_TO_TV_CHANNEL:
tvChannelHelper.addGameToChannel(computer, app.app);
return true;
default:
return super.onContextItemSelected(item);
}

View File

@ -268,10 +268,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return;
}
// Add a launcher shortcut for this PC (forced, since this is user interaction)
// Report this shortcut being used
shortcutHelper = new ShortcutHelper(this);
shortcutHelper.createAppViewShortcut(uuid, pcName, uuid, true);
shortcutHelper.reportShortcutUsed(uuid);
if (appName != null) {
shortcutHelper.reportGameLaunched(uuid, pcName, ""+appId, appName);
}
// Initialize the MediaCodec helper before creating the decoder
GlPreferences glPrefs = GlPreferences.readPreferences(this);
@ -412,7 +414,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height)
.setRefreshRate(prefConfig.fps)
.setApp(new NvApp(appName, appId, willStreamHdr))
.setApp(new NvApp(appName != null ? appName : "app", appId, willStreamHdr))
.setBitrate(prefConfig.bitrate)
.setEnableSops(prefConfig.enableSops)
.enableLocalAudioPlayback(prefConfig.playHostAudio)

View File

@ -25,7 +25,6 @@ import com.limelight.utils.Dialog;
import com.limelight.utils.HelpLauncher;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.TvChannelHelper;
import com.limelight.utils.UiHelper;
import android.app.Activity;
@ -63,7 +62,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter;
private ShortcutHelper shortcutHelper;
private TvChannelHelper tvChannelHelper;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private boolean freezeUpdates, runningPolling, inForeground, completeOnCreateCalled;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@ -217,7 +215,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
completeOnCreateCalled = true;
shortcutHelper = new ShortcutHelper(this);
tvChannelHelper = new TvChannelHelper(this);
UiHelper.setLocale(this);
@ -443,7 +440,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (toastSuccess) {
// Open the app list after a successful pairing attempt
doAppList(computer);
doAppList(computer, true);
}
else {
// Start polling again if we're still in the foreground
@ -542,7 +539,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}).start();
}
private void doAppList(ComputerDetails computer) {
private void doAppList(ComputerDetails computer, boolean newlyPaired) {
if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return;
@ -555,6 +552,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Intent i = new Intent(this, AppView.class);
i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid);
i.putExtra(AppView.NEW_PAIR_EXTRA, newlyPaired);
startActivity(i);
}
@ -594,7 +592,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
return true;
case APP_LIST_ID:
doAppList(computer.details);
doAppList(computer.details, false);
return true;
case RESUME_ID:
@ -640,9 +638,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_deleted_pc));
//Delete channel of PC
tvChannelHelper.deleteChannel(details.uuid);
pcGridAdapter.removeComputer(computer);
pcGridAdapter.notifyDataSetChanged();
@ -671,7 +666,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Add a launcher shortcut for this PC
if (details.pairState == PairState.PAIRED) {
shortcutHelper.createAppViewShortcut(details.uuid, details, false);
shortcutHelper.createAppViewShortcutForOnlineHost(details);
}
if (existingEntry != null) {
@ -713,7 +708,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details);
doAppList(computer.details, false);
}
}
});

View File

@ -104,7 +104,7 @@ public class ShortcutTrampoline extends Activity {
if (appIdString != null && appIdString.length() > 0) {
if (details.runningGameId == 0 || details.runningGameId == Integer.parseInt(appIdString)) {
intentStack.add(ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder));
new NvApp(null, Integer.parseInt(appIdString), false), details, managerBinder));
// Close this activity
finish();
@ -115,7 +115,7 @@ public class ShortcutTrampoline extends Activity {
// Create the start intent immediately, so we can safely unbind the managerBinder
// below before we return.
final Intent startIntent = ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder);
new NvApp(null, Integer.parseInt(appIdString), false), details, managerBinder);
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
@Override
@ -155,7 +155,7 @@ public class ShortcutTrampoline extends Activity {
// If a game is running, we'll make the stream the top level activity
if (details.runningGameId != 0) {
intentStack.add(ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", details.runningGameId, false), details, managerBinder));
new NvApp(null, details.runningGameId, false), details, managerBinder));
}
// Now start the activities

View File

@ -1,7 +1,7 @@
package com.limelight.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
@ -22,9 +22,10 @@ import java.util.List;
public class ShortcutHelper {
private final ShortcutManager sm;
private final Context context;
private final Activity context;
private final TvChannelHelper tvChannelHelper;
public ShortcutHelper(Context context) {
public ShortcutHelper(Activity context) {
this.context = context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
sm = context.getSystemService(ShortcutManager.class);
@ -32,6 +33,7 @@ public class ShortcutHelper {
else {
sm = null;
}
this.tvChannelHelper = new TvChannelHelper(context);
}
@TargetApi(Build.VERSION_CODES.N_MR1)
@ -80,15 +82,20 @@ public class ShortcutHelper {
return false;
}
public void reportShortcutUsed(String id) {
public void reportShortcutUsed(String computerUuid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
if (getInfoForId(id) != null) {
sm.reportShortcutUsed(id);
if (getInfoForId(computerUuid) != null) {
sm.reportShortcutUsed(computerUuid);
}
}
}
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
public void reportGameLaunched(String computerUuid, String computerName, String appId, String appName) {
tvChannelHelper.createTvChannel(computerUuid, computerName);
tvChannelHelper.addGameToChannel(computerUuid, computerName, appId, appName);
}
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd, boolean newlyPaired) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
@ -122,10 +129,16 @@ public class ShortcutHelper {
}
}
}
if (newlyPaired) {
// Avoid hammering the channel API for each computer poll because it will throttle us
tvChannelHelper.createTvChannel(computerUuid, computerName);
tvChannelHelper.requestChannelOnHomeScreen(computerUuid);
}
}
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
createAppViewShortcut(id, details.name, details.uuid, forceAdd);
public void createAppViewShortcutForOnlineHost(ComputerDetails details) {
createAppViewShortcut(details.uuid, details.name, details.uuid, false, false);
}
@TargetApi(Build.VERSION_CODES.O)
@ -161,10 +174,11 @@ public class ShortcutHelper {
return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid, app.getAppName(), Integer.valueOf(app.getAppId()).toString());
}
public void disableShortcut(String id, CharSequence reason) {
public void disableShortcut(String uuid, CharSequence reason) {
tvChannelHelper.deleteChannel(uuid);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
if (getInfoForId(id) != null) {
sm.disableShortcuts(Collections.singletonList(id), reason);
if (getInfoForId(uuid) != null) {
sm.disableShortcuts(Collections.singletonList(uuid), reason);
}
}
}

View File

@ -1,12 +1,12 @@
package com.limelight.utils;
import android.annotation.TargetApi;
import android.app.UiModeManager;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
@ -21,99 +21,91 @@ import com.limelight.LimeLog;
import com.limelight.PosterContentProvider;
import com.limelight.R;
import com.limelight.ShortcutTrampoline;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
import java.io.IOException;
import java.io.OutputStream;
@TargetApi(Build.VERSION_CODES.O)
public class TvChannelHelper {
private static final int ASPECT_RATIO_MOVIE_POSTER = 5;
private static final int TYPE_GAME = 12;
public static final String[] CHANNEL_PROJECTION = {TvContract.Channels._ID, TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID};
public static final int INTERNAL_PROVIDER_ID_INDEX = 1;
public static final int ID_INDEX = 0;
private Context context;
private static final int INTERNAL_PROVIDER_ID_INDEX = 1;
private static final int ID_INDEX = 0;
private Activity context;
public TvChannelHelper(Context context) {
public TvChannelHelper(Activity context) {
this.context = context;
}
public boolean createTvChannel(String computerUuid, String computerName) {
if (!isSupport()) {
return false;
void requestChannelOnHomeScreen(String computerUuid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!isAndroidTV()) {
return;
}
Long channelId = getChannelId(computerUuid);
if (channelId == null) {
return;
}
Intent intent = new Intent(TvContract.ACTION_REQUEST_CHANNEL_BROWSABLE);
intent.putExtra(TvContract.EXTRA_CHANNEL_ID, getChannelId(computerUuid));
try {
context.startActivityForResult(intent, 0);
} catch (ActivityNotFoundException e) {
}
}
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.setAction(Intent.ACTION_DEFAULT);
ChannelBuilder builder = new ChannelBuilder()
.setType(TvContract.Channels.TYPE_PREVIEW)
.setDisplayName(computerName)
.setInternalProviderId(computerUuid)
.setAppLinkIntent(i);
Long channelId = getChannelId(computerUuid);
if (channelId != null) {
context.getContentResolver().update(TvContract.buildChannelUri(channelId),
builder.toContentValues(), null, null);
return false;
}
Uri channelUri = context.getContentResolver().insert(
TvContract.Channels.CONTENT_URI, builder.toContentValues());
if (channelUri != null) {
long id = ContentUris.parseId(channelUri);
updateChannelIcon(id);
return true;
}
return false;
}
private void updateChannelIcon(long id) {
Bitmap bitmap = drawableToBitmap(context.getResources().getDrawable(R.drawable.ic_channel));
void createTvChannel(String computerUuid, String computerName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!isAndroidTV()) {
return;
}
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.setAction(Intent.ACTION_DEFAULT);
ChannelBuilder builder = new ChannelBuilder()
.setType(TvContract.Channels.TYPE_PREVIEW)
.setDisplayName(computerName)
.setInternalProviderId(computerUuid)
.setAppLinkIntent(i);
Long channelId = getChannelId(computerUuid);
if (channelId != null) {
context.getContentResolver().update(TvContract.buildChannelUri(channelId),
builder.toContentValues(), null, null);
return;
}
Uri channelUri = context.getContentResolver().insert(
TvContract.Channels.CONTENT_URI, builder.toContentValues());
if (channelUri != null) {
long id = ContentUris.parseId(channelUri);
updateChannelIcon(id);
}
}
}
@TargetApi(Build.VERSION_CODES.O)
private void updateChannelIcon(long channelId) {
Bitmap logo = drawableToBitmap(context.getResources().getDrawable(R.drawable.ic_channel));
try {
storeChannelLogo(id, bitmap);
Uri localUri = TvContract.buildChannelLogoUri(channelId);
try (OutputStream outputStream = context.getContentResolver().openOutputStream(localUri)) {
logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.flush();
} catch (SQLiteException | IOException e) {
LimeLog.warning("Failed to store the logo to the system content provider.");
e.printStackTrace();
}
} finally {
bitmap.recycle();
logo.recycle();
}
}
/**
* Stores the given channel logo {@link Bitmap} in the system content provider and associate
* it with the given channel ID.
*
* @param channelId the ID of the target channel with which the given logo should be associated
* @param logo the logo image to be stored
* @return {@code true} if successfully stored the logo in the system content provider,
* otherwise {@code false}.
*/
private boolean storeChannelLogo(long channelId,
Bitmap logo) {
if (!isSupport()) {
return false;
}
boolean result = false;
Uri localUri = TvContract.buildChannelLogoUri(channelId);
try (OutputStream outputStream = context.getContentResolver().openOutputStream(localUri)) {
result = logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.flush();
} catch (SQLiteException | IOException e) {
LimeLog.warning("Failed to store the logo to the system content provider.");
e.printStackTrace();
}
return result;
}
private Bitmap drawableToBitmap(Drawable drawable) {
int width = context.getResources().getDimensionPixelSize(R.dimen.tv_channel_logo_width);
int height = context.getResources().getDimensionPixelSize(R.dimen.tv_channel_logo_width);
@ -125,59 +117,62 @@ public class TvChannelHelper {
return bitmap;
}
void addGameToChannel(String computerUuid, String computerName, String appId, String appName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!isAndroidTV()) {
return;
}
public boolean addGameToChannel(String computerUuid, String computerName, String appId, String appName) {
if (!isSupport()) {
return false;
PreviewProgramBuilder builder = new PreviewProgramBuilder();
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.putExtra(ShortcutTrampoline.APP_ID_EXTRA, appId);
i.setAction(Intent.ACTION_DEFAULT);
Uri resourceURI = PosterContentProvider.createBoxArtUri(computerUuid, appId);
Long channelId = getChannelId(computerUuid);
if (channelId == null) {
return;
}
builder.setChannelId(channelId)
.setType(TYPE_GAME)
.setTitle(appName)
.setPosterArtAspectRatio(ASPECT_RATIO_MOVIE_POSTER)
.setPosterArtUri(resourceURI)
.setIntent(i)
.setInternalProviderId(appId);
context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
builder.toContentValues());
TvContract.requestChannelBrowsable(context, channelId);
}
PreviewProgramBuilder builder = new PreviewProgramBuilder();
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.putExtra(ShortcutTrampoline.APP_ID_EXTRA, appId);
i.setAction(Intent.ACTION_DEFAULT);
Uri resourceURI = PosterContentProvider.createBoxArtUri(computerUuid, appId);
Long channelId = getChannelId(computerUuid);
if (channelId == null) {
return false;
}
builder.setChannelId(channelId)
.setType(TYPE_GAME)
.setTitle(appName)
.setPosterArtAspectRatio(ASPECT_RATIO_MOVIE_POSTER)
.setPosterArtUri(resourceURI)
.setIntent(i)
.setInternalProviderId(appId);
Uri programUri = context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
builder.toContentValues());
TvContract.requestChannelBrowsable(context, channelId);
return programUri != null;
}
public boolean deleteChannel(String computerUuid) {
if (!isSupport()) {
return false;
}
Long channelId = getChannelId(computerUuid);
if (channelId == null) {
return false;
}
Uri uri = TvContract.buildChannelUri(channelId);
return context.getContentResolver().delete(uri, null, null) > 0;
void deleteChannel(String computerUuid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!isAndroidTV()) {
return;
}
Long channelId = getChannelId(computerUuid);
if (channelId == null) {
return;
}
Uri uri = TvContract.buildChannelUri(channelId);
context.getContentResolver().delete(uri, null, null);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@TargetApi(Build.VERSION_CODES.O)
private Long getChannelId(String computerUuid) {
Uri uri = TvContract.Channels.CONTENT_URI;
final String[] CHANNEL_PROJECTION = {TvContract.Channels._ID, TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID};
Cursor cursor = context.getContentResolver().query(uri,
CHANNEL_PROJECTION,
null,
@ -202,11 +197,6 @@ public class TvChannelHelper {
}
}
public void addGameToChannel(ComputerDetails computer, NvApp app) {
addGameToChannel(computer.uuid, computer.name, String.valueOf(app.getAppId()), app.getAppName());
}
private static <T> String toValueString(T value) {
return value == null ? null : value.toString();
}
@ -215,15 +205,12 @@ public class TvChannelHelper {
return intent == null ? null : intent.toUri(Intent.URI_INTENT_SCHEME);
}
public boolean isSupport() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false;
}
UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
@TargetApi(Build.VERSION_CODES.O)
public boolean isAndroidTV() {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
@TargetApi(Build.VERSION_CODES.O)
private static class PreviewProgramBuilder {
private ContentValues mValues = new ContentValues();
@ -275,6 +262,7 @@ public class TvChannelHelper {
}
@TargetApi(Build.VERSION_CODES.O)
private static class ChannelBuilder {
private ContentValues mValues = new ContentValues();