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

@@ -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();