package tv.emby.embyatv.util; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.ToneGenerator; import android.os.Build; import android.text.InputType; import android.text.TextUtils; import android.view.Display; import android.view.View; import android.view.WindowManager; import android.widget.EditText; import android.widget.PopupMenu; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.TimeZone; import java.util.UUID; import mediabrowser.apiinteraction.ApiClient; import mediabrowser.apiinteraction.ConnectionResult; import mediabrowser.apiinteraction.EmptyResponse; import mediabrowser.apiinteraction.IConnectionManager; import mediabrowser.apiinteraction.Response; import mediabrowser.apiinteraction.android.GsonJsonSerializer; import mediabrowser.apiinteraction.android.profiles.AndroidProfileOptions; import mediabrowser.model.apiclient.ServerInfo; import mediabrowser.model.dlna.StreamInfo; import mediabrowser.model.dto.BaseItemDto; import mediabrowser.model.dto.BaseItemPerson; import mediabrowser.model.dto.ChapterInfoDto; import mediabrowser.model.dto.ImageOptions; import mediabrowser.model.dto.MediaSourceInfo; import mediabrowser.model.dto.StudioDto; import mediabrowser.model.dto.UserDto; import mediabrowser.model.dto.UserItemDataDto; import mediabrowser.model.entities.ImageType; import mediabrowser.model.entities.LocationType; import mediabrowser.model.entities.MediaStream; import mediabrowser.model.entities.MediaStreamType; import mediabrowser.model.library.PlayAccess; import mediabrowser.model.livetv.ChannelInfoDto; import mediabrowser.model.logging.ILogger; import mediabrowser.model.querying.ItemFields; import mediabrowser.model.querying.ItemFilter; import mediabrowser.model.querying.ItemQuery; import mediabrowser.model.querying.ItemSortBy; import mediabrowser.model.querying.ItemsResult; import mediabrowser.model.querying.SimilarItemsQuery; import mediabrowser.model.session.PlaybackProgressInfo; import mediabrowser.model.session.PlaybackStartInfo; import mediabrowser.model.session.PlaybackStopInfo; import mediabrowser.model.users.AuthenticationResult; import tv.emby.embyatv.BuildConfig; import tv.emby.embyatv.R; import tv.emby.embyatv.TvApp; import tv.emby.embyatv.browsing.MainActivity; import tv.emby.embyatv.details.FullDetailsActivity; import tv.emby.embyatv.details.ItemListActivity; import tv.emby.embyatv.model.ChapterItemInfo; import tv.emby.embyatv.playback.MediaManager; import tv.emby.embyatv.playback.PlaybackOverlayActivity; import tv.emby.embyatv.startup.ConnectActivity; import tv.emby.embyatv.startup.DpadPwActivity; import tv.emby.embyatv.startup.LogonCredentials; import tv.emby.embyatv.startup.SelectServerActivity; import tv.emby.embyatv.startup.SelectUserActivity; /** * A collection of utility methods, all static. */ public class Utils { /* * Making sure public utility methods remain static */ private Utils() { } private static int maxPrimaryImageHeight = 370; /** * Returns the screen/display size * * @param context * @return */ public static Point getDisplaySize(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x; int height = size.y; return new Point(width, height); } /** * Shows an error dialog with a given text message. * * @param context * @param errorString */ public static final void showErrorDialog(Context context, String errorString) { new AlertDialog.Builder(context).setTitle(R.string.error) .setMessage(errorString) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }) .create() .show(); } /** * Shows a (long) toast * * @param context * @param msg */ public static void showToast(Context context, String msg) { Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); } /** * Shows a (long) toast. * * @param context * @param resourceId */ public static void showToast(Context context, int resourceId) { Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show(); } public static byte[] encrypt(String x) throws Exception { java.security.MessageDigest d = null; d = java.security.MessageDigest.getInstance("SHA-1"); d.reset(); d.update(x.getBytes()); return d.digest(); } /** * Formats time in milliseconds to hh:mm:ss string format. * * @param millis * @return */ public static String formatMillis(int millis) { String result = ""; int hr = millis / 3600000; millis %= 3600000; int min = millis / 60000; millis %= 60000; int sec = millis / 1000; if (hr > 0) { result += hr + ":"; } if (min >= 0) { if (min > 9) { result += min + ":"; } else { result += "0" + min + ":"; } } if (sec > 9) { result += sec; } else { result += "0" + sec; } return result; } /** * Formats time in milliseconds to hh:mm:ss string format. * * @param millis * @return */ public static String formatMillis(long millis) { String result = ""; long hr = millis / 3600000; millis %= 3600000; long min = millis / 60000; millis %= 60000; long sec = millis / 1000; if (hr > 0) { result += hr + ":"; } if (min >= 0) { if (min > 9) { result += min + ":"; } else { result += (hr > 0 ? "0" : "") + min + ":"; } } if (sec > 9) { result += sec; } else { result += "0" + sec; } return result; } public static int convertDpToPixel(Context ctx, int dp) { float density = ctx.getResources().getDisplayMetrics().density; return Math.round((float) dp * density); } public static Drawable writeOnDrawable(Resources resources, int drawableId, String text){ Bitmap bm = BitmapFactory.decodeResource(resources, drawableId).copy(Bitmap.Config.ARGB_8888, true); Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.WHITE); paint.setTextSize(30); Canvas canvas = new Canvas(bm); canvas.drawText(text, 0, bm.getHeight()/2, paint); return new BitmapDrawable(resources, bm); } public static boolean isLiveTv(BaseItemDto item) { return "Program".equals(item.getType()) || "LiveTvChannel".equals(item.getType()); } private static String[] ThumbFallbackTypes = new String[] {"Episode"}; public static Double getImageAspectRatio(BaseItemDto item, boolean preferParentThumb) { if (preferParentThumb && (item.getParentThumbItemId() != null || item.getSeriesThumbImageTag() != null)) return 1.779; if (Arrays.asList(ThumbFallbackTypes).contains(item.getType())) { if (item.getPrimaryImageAspectRatio() != null) return item.getPrimaryImageAspectRatio(); if (item.getParentThumbItemId() != null || item.getSeriesThumbImageTag() != null) return 1.779; } return item.getPrimaryImageAspectRatio() != null ? item.getPrimaryImageAspectRatio() : .77777; } public static String getPrimaryImageUrl(BaseItemPerson item, ApiClient apiClient, int maxHeight) { ImageOptions options = new ImageOptions(); options.setTag(item.getPrimaryImageTag()); options.setMaxHeight(maxHeight); options.setImageType(ImageType.Primary); return apiClient.GetPersonImageUrl(item, options); } public static String getPrimaryImageUrl(StudioDto studio, ApiClient apiClient, int maxHeight) { ImageOptions options = new ImageOptions(); options.setTag(studio.getPrimaryImageTag()); options.setMaxHeight(maxHeight); options.setImageType(ImageType.Primary); return apiClient.GetImageUrl(studio.getId(), options); } public static String getPrimaryImageUrl(UserDto item, ApiClient apiClient) { ImageOptions options = new ImageOptions(); options.setTag(item.getPrimaryImageTag()); options.setMaxHeight(maxPrimaryImageHeight); options.setImageType(ImageType.Primary); return apiClient.GetUserImageUrl(item, options); } public static String getPrimaryImageUrl(BaseItemDto item, int width, int height) { if (!item.getHasPrimaryImage()) return null; ImageOptions options = new ImageOptions(); options.setTag(item.getImageTags().get(ImageType.Primary)); options.setMaxWidth(width); options.setMaxHeight(height); options.setImageType(ImageType.Primary); return TvApp.getApplication().getApiClient().GetImageUrl(item, options); } public static String getPrimaryImageUrl(BaseItemDto item, ApiClient apiClient) { if (!item.getHasPrimaryImage()) return null; ImageOptions options = new ImageOptions(); options.setTag(item.getImageTags().get(ImageType.Primary)); options.setMaxHeight(maxPrimaryImageHeight); options.setImageType(ImageType.Primary); return apiClient.GetImageUrl(item, options); } public static String getPrimaryImageUrl(ChannelInfoDto item, ApiClient apiClient) { if (!item.getHasPrimaryImage()) return null; ImageOptions options = new ImageOptions(); options.setTag(item.getImageTags().get(ImageType.Primary)); options.setMaxHeight(maxPrimaryImageHeight); options.setImageType(ImageType.Primary); return apiClient.GetImageUrl(item, options); } private static String[] ProgressIndicatorTypes = new String[] {"Episode", "Movie", "MusicVideo", "Video"}; public static String getImageUrl(String itemId, ImageType imageType, String imageTag, ApiClient apiClient) { ImageOptions options = new ImageOptions(); options.setMaxHeight(maxPrimaryImageHeight); options.setImageType(imageType); options.setTag(imageTag); return apiClient.GetImageUrl(itemId, options); } public static String getBannerImageUrl(BaseItemDto item, ApiClient apiClient, int maxHeight) { if (!item.getHasBanner()) return getPrimaryImageUrl(item, apiClient, !"MusicArtist".equals(item.getType()) && !"MusicAlbum".equals(item.getType()), false, maxHeight); ImageOptions options = new ImageOptions(); options.setTag(item.getImageTags().get(ImageType.Banner)); options.setImageType(ImageType.Banner); UserItemDataDto userData = item.getUserData(); if (userData != null && !"MusicArtist".equals(item.getType()) && !"MusicAlbum".equals(item.getType())) { if (Arrays.asList(ProgressIndicatorTypes).contains(item.getType()) && userData.getPlayedPercentage() != null && userData.getPlayedPercentage() > 0 && userData.getPlayedPercentage() < 99) { Double pct = userData.getPlayedPercentage(); options.setPercentPlayed(pct.intValue()); } options.setAddPlayedIndicator(userData.getPlayed()); if (item.getIsFolder() && userData.getUnplayedItemCount() != null && userData.getUnplayedItemCount() > 0) options.setUnPlayedCount(userData.getUnplayedItemCount()); } return apiClient.GetImageUrl(item.getId(), options); } public static String getThumbImageUrl(BaseItemDto item, ApiClient apiClient, int maxHeight) { if (!item.getHasThumb()) return getPrimaryImageUrl(item, apiClient, !"MusicArtist".equals(item.getType()) && !"MusicAlbum".equals(item.getType()), true, maxHeight); ImageOptions options = new ImageOptions(); options.setTag(item.getImageTags().get(ImageType.Thumb)); options.setImageType(ImageType.Thumb); UserItemDataDto userData = item.getUserData(); if (userData != null && !"MusicArtist".equals(item.getType()) && !"MusicAlbum".equals(item.getType())) { if (Arrays.asList(ProgressIndicatorTypes).contains(item.getType()) && userData.getPlayedPercentage() != null && userData.getPlayedPercentage() > 0 && userData.getPlayedPercentage() < 99) { Double pct = userData.getPlayedPercentage(); options.setPercentPlayed(pct.intValue()); } options.setAddPlayedIndicator(userData.getPlayed()); if (item.getIsFolder() && userData.getUnplayedItemCount() != null && userData.getUnplayedItemCount() > 0) options.setUnPlayedCount(userData.getUnplayedItemCount()); } return apiClient.GetImageUrl(item.getId(), options); } public static String getPrimaryImageUrl(BaseItemDto item, ApiClient apiClient, Boolean showWatched, boolean preferParentThumb, int maxHeight) { return getPrimaryImageUrl(item, apiClient, showWatched, true, preferParentThumb, false, maxHeight); } public static String getPrimaryImageUrl(BaseItemDto item, ApiClient apiClient, Boolean showWatched, boolean showProgress, boolean preferParentThumb, boolean preferSeriesPoster, int maxHeight) { ImageOptions options = new ImageOptions(); String itemId = item.getId(); String imageTag = item.getImageTags() != null ? item.getImageTags().get(ImageType.Primary) : null; ImageType imageType = ImageType.Primary; if (preferSeriesPoster && item.getType().equals("Episode") && item.getSeasonId() != null) { imageTag = null; itemId = item.getSeasonId(); } else if (preferSeriesPoster && item.getType().equals("Episode") && item.getSeriesPrimaryImageTag() != null && item.getSeriesId() != null) { imageTag = item.getSeriesPrimaryImageTag(); itemId = item.getSeriesId(); } else if (preferParentThumb || (item.getType().equals("Episode") && imageTag == null)) { //try for thumb of season or series if (item.getParentThumbImageTag() != null) { imageTag = item.getParentThumbImageTag(); itemId = item.getParentThumbItemId(); imageType = ImageType.Thumb; } else if (item.getSeriesThumbImageTag() != null) { imageTag = item.getSeriesThumbImageTag(); itemId = item.getSeriesId(); imageType = ImageType.Thumb; } } else { if (item.getType().equals("Season") && imageTag == null) { imageTag = item.getSeriesPrimaryImageTag(); itemId = item.getSeriesId(); } } if ("Audio".equals(item.getType()) && !item.getHasPrimaryImage()) { //Try the album or artist if (item.getAlbumId() != null && item.getAlbumPrimaryImageTag() != null) { imageTag = item.getAlbumPrimaryImageTag(); itemId = item.getAlbumId(); imageType = ImageType.Primary; } else if (item.getAlbumArtists() != null && item.getAlbumArtists().size() > 0) { itemId = item.getAlbumArtists().get(0).getId(); imageTag = null; } } options.setMaxHeight(maxHeight); options.setImageType(imageType); UserItemDataDto userData = item.getUserData(); if (userData != null) { if (showProgress && Arrays.asList(ProgressIndicatorTypes).contains(item.getType()) && userData.getPlayedPercentage() != null && userData.getPlayedPercentage() > 0 && userData.getPlayedPercentage() < 99) { Double pct = userData.getPlayedPercentage(); options.setPercentPlayed(pct.intValue()); } if (showWatched) { options.setAddPlayedIndicator(userData.getPlayed()); if (item.getIsFolder() && userData.getUnplayedItemCount() != null && userData.getUnplayedItemCount() > 0) options.setUnPlayedCount(userData.getUnplayedItemCount()); } } options.setTag(imageTag); return apiClient.GetImageUrl(itemId, options); } public static String getLogoImageUrl(BaseItemDto item, ApiClient apiClient) { if (item != null) { ImageOptions options = new ImageOptions(); options.setMaxWidth(440); options.setImageType(ImageType.Logo); if (item.getHasLogo()) { options.setTag(item.getImageTags().get(ImageType.Logo)); return apiClient.GetImageUrl(item, options); } else if (item.getParentLogoImageTag() != null) { options.setTag(item.getParentLogoImageTag()); return apiClient.GetImageUrl(item.getParentLogoItemId(), options); } else if (item.getSeriesId() != null) { options.setTag(null); return apiClient.GetImageUrl(item.getSeriesId(), options); } } return null; } public static String getBackdropImageUrl(BaseItemDto item, ApiClient apiClient, boolean random) { if (item != null) { if (item.getBackdropCount() > 0) { ImageOptions options = new ImageOptions(); options.setMaxWidth(1200); options.setImageType(ImageType.Backdrop); int index = random ? randInt(0, item.getBackdropCount() - 1) : 0; options.setImageIndex(index); options.setTag(item.getBackdropImageTags().get(index)); return apiClient.GetImageUrl(item, options); } else { if (item.getParentBackdropImageTags() != null && item.getParentBackdropImageTags().size() > 0) { ImageOptions options = new ImageOptions(); options.setMaxWidth(1200); options.setImageType(ImageType.Backdrop); int index = random ? randInt(0, item.getParentBackdropImageTags().size() - 1) : 0; options.setImageIndex(index); options.setTag(item.getParentBackdropImageTags().get(index)); return apiClient.GetImageUrl(item.getParentBackdropItemId(), options); } } } return null; } public static void getItemsToPlay(final BaseItemDto mainItem, boolean allowIntros, final boolean shuffle, final Response<List<BaseItemDto>> outerResponse) { final List<BaseItemDto> items = new ArrayList<>(); ItemQuery query = new ItemQuery(); switch (mainItem.getType()) { case "Episode": items.add(mainItem); if (TvApp.getApplication().getPrefs().getBoolean("pref_enable_tv_queuing", true)) { MediaManager.setVideoQueueModified(false); // we are automatically creating new queue //add subsequent episodes if (mainItem.getSeasonId() != null && mainItem.getIndexNumber() != null) { query.setParentId(mainItem.getSeasonId()); query.setIsVirtualUnaired(false); query.setMinIndexNumber(mainItem.getIndexNumber() + 1); query.setSortBy(new String[] {ItemSortBy.SortName}); query.setIncludeItemTypes(new String[]{"Episode"}); query.setFields(new ItemFields[] {ItemFields.MediaSources, ItemFields.MediaStreams, ItemFields.Path, ItemFields.Chapters, ItemFields.Overview, ItemFields.PrimaryImageAspectRatio}); query.setUserId(TvApp.getApplication().getCurrentUser().getId()); TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { for (BaseItemDto item : response.getItems()) { if (item.getIndexNumber() > mainItem.getIndexNumber()) { if (!LocationType.Virtual.equals(item.getLocationType())) { items.add(item); } else { //stop adding when we hit a missing one break; } } } } outerResponse.onResponse(items); } }); } else { TvApp.getApplication().getLogger().Info("Unable to add subsequent episodes due to lack of season or episode data."); outerResponse.onResponse(items); } } else { outerResponse.onResponse(items); } break; case "Series": case "Season": case "BoxSet": case "Folder": //get all videos query.setParentId(mainItem.getId()); query.setIsMissing(false); query.setIsVirtualUnaired(false); query.setIncludeItemTypes(new String[]{"Episode", "Movie", "Video"}); query.setSortBy(new String[]{shuffle ? ItemSortBy.Random : ItemSortBy.SortName}); query.setRecursive(true); query.setLimit(50); // guard against too many items query.setFields(new ItemFields[] {ItemFields.MediaSources, ItemFields.MediaStreams, ItemFields.Chapters, ItemFields.Path, ItemFields.PrimaryImageAspectRatio}); query.setUserId(TvApp.getApplication().getCurrentUser().getId()); TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { Collections.addAll(items, response.getItems()); outerResponse.onResponse(items); } }); break; case "MusicAlbum": case "MusicArtist": //get all songs query.setIsMissing(false); query.setIsVirtualUnaired(false); query.setIncludeItemTypes(new String[]{"Audio"}); query.setSortBy(shuffle ? new String[] {ItemSortBy.Random} : "MusicArtist".equals(mainItem.getType()) ? new String[] {ItemSortBy.Album} : new String[] {ItemSortBy.SortName}); query.setRecursive(true); query.setLimit(150); // guard against too many items query.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Genres}); query.setUserId(TvApp.getApplication().getCurrentUser().getId()); TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { outerResponse.onResponse(Arrays.asList(response.getItems())); } }); break; case "Playlist": if (mainItem.getId().equals(ItemListActivity.FAV_SONGS)) { query.setFilters(new ItemFilter[] {ItemFilter.IsFavoriteOrLikes}); } else { query.setParentId(mainItem.getId()); } query.setIsMissing(false); query.setIsVirtualUnaired(false); if (shuffle) query.setSortBy(new String[] {ItemSortBy.Random}); query.setRecursive(true); query.setLimit(150); // guard against too many items query.setFields(new ItemFields[] {ItemFields.MediaSources, ItemFields.MediaStreams, ItemFields.Chapters, ItemFields.Path, ItemFields.PrimaryImageAspectRatio}); query.setUserId(TvApp.getApplication().getCurrentUser().getId()); TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { outerResponse.onResponse(Arrays.asList(response.getItems())); } }); break; case "Program": if (mainItem.getParentId() == null) { outerResponse.onError(new Exception("No Channel ID")); return; } //We retrieve the channel the program is on (which should be the program's parent) TvApp.getApplication().getApiClient().GetItemAsync(mainItem.getParentId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() { @Override public void onResponse(BaseItemDto response) { // fill in info about the specific program for display response.setPremiereDate(mainItem.getPremiereDate()); response.setEndDate(mainItem.getEndDate()); response.setOfficialRating(mainItem.getOfficialRating()); response.setRunTimeTicks(mainItem.getRunTimeTicks()); items.add(response); outerResponse.onResponse(items); } @Override public void onError(Exception exception) { super.onError(exception); } }); break; case "TvChannel": // Retrieve full channel info for display TvApp.getApplication().getApiClient().GetLiveTvChannelAsync(mainItem.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<ChannelInfoDto>() { @Override public void onResponse(ChannelInfoDto response) { // get current program info and fill it into our item BaseItemDto program = response.getCurrentProgram(); if (program != null) { mainItem.setPremiereDate(program.getStartDate()); mainItem.setEndDate(program.getEndDate()); mainItem.setOfficialRating(program.getOfficialRating()); mainItem.setRunTimeTicks(program.getRunTimeTicks()); } addMainItem(mainItem, items, outerResponse); } }); break; default: if (allowIntros && TvApp.getApplication().getPrefs().getBoolean("pref_enable_cinema_mode", true)) { //Intros TvApp.getApplication().getApiClient().GetIntrosAsync(mainItem.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0){ Collections.addAll(items, response.getItems()); TvApp.getApplication().getLogger().Info(response.getTotalRecordCount() + " intro items added for playback."); } //Finally, the main item including subsequent parts addMainItem(mainItem, items, outerResponse); } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving intros", exception); addMainItem(mainItem, items, outerResponse); } }); } else { addMainItem(mainItem, items, outerResponse); } break; } } public static void play(final BaseItemDto item, final int pos, final boolean shuffle, final Context activity) { Utils.getItemsToPlay(item, pos == 0 && item.getType().equals("Movie"), shuffle, new Response<List<BaseItemDto>>() { @Override public void onResponse(List<BaseItemDto> response) { switch (item.getType()) { case "MusicAlbum": case "MusicArtist": MediaManager.playNow(response); break; case "Playlist": if ("Audio".equals(item.getMediaType())) { MediaManager.playNow(response); } else { Intent intent = new Intent(activity, PlaybackOverlayActivity.class); MediaManager.setCurrentVideoQueue(response); intent.putExtra("Position", pos); if (!(activity instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivity(intent); } break; case "Audio": if (response.size() > 0) { MediaManager.playNow(response.get(0)); } break; default: Intent intent = new Intent(activity, PlaybackOverlayActivity.class); MediaManager.setCurrentVideoQueue(response); intent.putExtra("Position", pos); if (!(activity instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivity(intent); } } }); } public static void retrieveAndPlay(String id, boolean shuffle, Context activity) { retrieveAndPlay(id, shuffle, null, activity); } public static void retrieveAndPlay(String id, final boolean shuffle, final Long position, final Context activity) { TvApp.getApplication().getApiClient().GetItemAsync(id, TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() { @Override public void onResponse(BaseItemDto response) { Long pos = position != null ? position / 10000 : response.getUserData() != null ? response.getUserData().getPlaybackPositionTicks() / 10000 : 0; play(response, pos.intValue(), shuffle, activity); } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving item for playback", exception); Utils.showToast(activity, R.string.msg_video_playback_error); } }); } public static void playInstantMix(String seedId) { getInstantMixAsync(seedId, new Response<BaseItemDto[]>() { @Override public void onResponse(BaseItemDto[] response) { if (response.length > 0) { MediaManager.playNow(Arrays.asList(response)); } else { showToast(TvApp.getApplication(), R.string.msg_no_playable_items); } } }); } public static void getInstantMixAsync(String seedId, final Response<BaseItemDto[]> outerResponse) { SimilarItemsQuery query = new SimilarItemsQuery(); query.setId(seedId); query.setUserId(TvApp.getApplication().getCurrentUser().getId()); query.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Genres}); TvApp.getApplication().getApiClient().GetInstantMixFromItem(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { outerResponse.onResponse(response.getItems()); } @Override public void onError(Exception exception) { outerResponse.onError(exception); } }); } public static String getStoreUrl() { return isFireTv() ? "http://www.amazon.com/Emby-for-Fire-TV/dp/B00VVJKTW8/ref=sr_1_2?s=mobile-apps&ie=UTF8&qid=1430569449&sr=1-2" : "https://play.google.com/store/apps/details?id=tv.emby.embyatv"; } private static void addMainItem(BaseItemDto mainItem, final List<BaseItemDto> items, final Response<List<BaseItemDto>> outerResponse) { items.add(mainItem); if (mainItem.getPartCount() != null && mainItem.getPartCount() > 1) { // get additional parts TvApp.getApplication().getApiClient().GetAdditionalParts(mainItem.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { Collections.addAll(items, response.getItems()); outerResponse.onResponse(items); } }); } else { outerResponse.onResponse(items); } } public static boolean CanPlay(BaseItemDto item) { return item.getPlayAccess().equals(PlayAccess.Full) && ((item.getIsPlaceHolder() == null || !item.getIsPlaceHolder()) && (!item.getType().equals("Episode") || !item.getLocationType().equals(LocationType.Virtual))) && (!item.getType().equals("Person")) && (!item.getIsFolder() || item.getChildCount() == null || item.getChildCount() > 0); } private static String divider = " | "; public static String getInfoRow(BaseItemDto item) { StringBuilder sb = new StringBuilder(); String type = item.getType(); switch (type) { case "Person": if (item.getPremiereDate() != null) { sb.append(TvApp.getApplication().getString(R.string.lbl_born)); sb.append(new SimpleDateFormat("d MMM y").format(convertToLocalDate(item.getPremiereDate()))); } if (item.getEndDate() != null) { addWithDivider(sb, "Died "); sb.append(new SimpleDateFormat("d MMM y").format(item.getEndDate())); sb.append(" ("); sb.append(numYears(item.getPremiereDate(), item.getEndDate())); sb.append(")"); } else { if (item.getPremiereDate() != null) { sb.append(" ("); sb.append(numYears(item.getPremiereDate(), Calendar.getInstance())); sb.append(")"); } } break; default: if (item.getType().equals("Episode")) { sb.append("S"); sb.append(item.getParentIndexNumber()); sb.append(", E"); sb.append(item.getIndexNumber()); } if (item.getCommunityRating() != null) { addWithDivider(sb, item.getCommunityRating()); } if (item.getCriticRating() != null) { if (sb.length() > 0) sb.append(" / "); sb.append(item.getCriticRating()); sb.append("%"); } MediaStream video = null; if (item.getMediaStreams() != null) { for (MediaStream stream : item.getMediaStreams()) { if (stream.getType() == MediaStreamType.Video) { video = stream; break; } } } if (video != null) { if (video.getWidth() > 1910) { addWithDivider(sb, "1080"); } else if (video.getWidth() > 1270) { addWithDivider(sb, "720"); } } if (item.getRunTimeTicks() != null && item.getRunTimeTicks() > 0) { addWithDivider(sb, item.getRunTimeTicks() / 600000000); sb.append("m"); } if (item.getOfficialRating() != null && !item.getOfficialRating().equals("0")) { addWithDivider(sb, item.getOfficialRating()); } switch (item.getType()) { case "Series": if (item.getAirDays() != null && item.getAirDays().size() > 0) { addWithDivider(sb, item.getAirDays().get(0)); sb.append(" "); } if (item.getAirTime() != null) { sb.append(item.getAirTime()); } if (item.getStatus() != null) { addWithDivider(sb, item.getStatus()); } break; case "Program": case "TvChannel": if (item.getPremiereDate() != null && item.getEndDate() != null) { addWithDivider(sb, android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(convertToLocalDate(item.getPremiereDate())) + "-"+ android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(convertToLocalDate(item.getEndDate()))); } break; default: if (item.getPremiereDate() != null) { addWithDivider(sb, new SimpleDateFormat("d MMM y").format(convertToLocalDate(item.getPremiereDate()))); } } } return sb.toString(); } public static int numYears(Date start, Date end) { Calendar calStart = Calendar.getInstance(); calStart.setTime(start); Calendar calEnd = Calendar.getInstance(); calEnd.setTime(end); return numYears(calStart, calEnd); } public static int numYears(Date start, Calendar end) { Calendar calStart = Calendar.getInstance(); calStart.setTime(start); return numYears(calStart, end); } public static int numYears(Calendar start, Calendar end) { int age = end.get(Calendar.YEAR) - start.get(Calendar.YEAR); if (end.get(Calendar.MONTH) < start.get(Calendar.MONTH)) { age--; } else if (end.get(Calendar.MONTH) == start.get(Calendar.MONTH) && end.get(Calendar.DAY_OF_MONTH) < start.get(Calendar.DAY_OF_MONTH)) { age--; } return age; } private static void addWithDivider(StringBuilder sb, String value) { if (sb.length() > 0) { sb.append(divider); } sb.append(value); } private static void addWithDivider(StringBuilder sb, Long value) { if (sb.length() > 0) { sb.append(divider); } sb.append(value); } private static void addWithDivider(StringBuilder sb, Float value) { if (sb.length() > 0) { sb.append(divider); } sb.append(value); } public static String GetFullName(BaseItemDto item) { switch (item.getType()) { case "Episode": return item.getSeriesName() + " S" + item.getParentIndexNumber() + ", E" + item.getIndexNumber() + (item.getIndexNumberEnd() != null ? "-" + item.getIndexNumberEnd() : ""); case "Audio": case "MusicAlbum": // we actually want the artist name if available return (item.getAlbumArtist() != null ? item.getAlbumArtist() + " - " : "") + item.getName(); default: return item.getName(); } } public static String GetSubName(BaseItemDto item) { switch (item.getType()) { case "Episode": String addendum = item.getLocationType().equals(LocationType.Virtual) && item.getPremiereDate() != null ? " (" + getFriendlyDate(Utils.convertToLocalDate(item.getPremiereDate())) + ")" : ""; return item.getName() + addendum; case "Season": return item.getChildCount() != null && item.getChildCount() > 0 ? item.getChildCount() + " " + TvApp.getApplication().getString(R.string.lbl_episodes) : ""; case "MusicAlbum": return item.getChildCount() != null && item.getChildCount() > 0 ? item.getChildCount() + " " + TvApp.getApplication().getString(item.getChildCount() > 1 ? R.string.lbl_songs : R.string.lbl_song) : ""; case "Audio": return item.getName(); default: return item.getOfficialRating(); } } public static String GetProgramSubText(BaseItemDto baseItem) { Calendar start = Calendar.getInstance(); start.setTime(Utils.convertToLocalDate(baseItem.getStartDate())); int day = start.get(Calendar.DAY_OF_YEAR); return baseItem.getChannelName() + " - " + (baseItem.getEpisodeTitle() != null ? baseItem.getEpisodeTitle() : "") + " " + ((day != Calendar.getInstance().get(Calendar.DAY_OF_YEAR) ? getFriendlyDate(start.getTime()) + " " : "") + android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(start.getTime()) + "-" + android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(Utils.convertToLocalDate(baseItem.getEndDate()))); } public static BaseItemPerson GetFirstPerson(BaseItemDto item, String type) { if (item.getPeople() == null || item.getPeople().length < 1) return null; for (BaseItemPerson person : item.getPeople()) { if (type.equals(person.getType())) return person; } return null; } public static List<ChapterItemInfo> buildChapterItems(BaseItemDto item) { List<ChapterItemInfo> chapters = new ArrayList<>(); ImageOptions options = new ImageOptions(); options.setImageType(ImageType.Chapter); int i = 0; for (ChapterInfoDto dto : item.getChapters()) { ChapterItemInfo chapter = new ChapterItemInfo(); chapter.setItemId(item.getId()); chapter.setName(dto.getName()); chapter.setStartPositionTicks(dto.getStartPositionTicks()); if (dto.getHasImage()) { options.setTag(dto.getImageTag()); options.setImageIndex(i); chapter.setImagePath(TvApp.getApplication().getApiClient().GetImageUrl(item.getId(), options)); } chapters.add(chapter); i++; } return chapters; } public static MediaStream GetMediaStream(MediaSourceInfo mediaSource, int index) { if (mediaSource.getMediaStreams() == null || mediaSource.getMediaStreams().size() == 0) return null; for (MediaStream stream : mediaSource.getMediaStreams()) { if (stream.getIndex() == index) return stream; } return null; } public static List<MediaStream> GetSubtitleStreams(MediaSourceInfo mediaSource) { return GetStreams(mediaSource, MediaStreamType.Subtitle); } public static List<MediaStream> GetAudioStreams(MediaSourceInfo mediaSource) { return GetStreams(mediaSource, MediaStreamType.Audio); } public static MediaStream GetFirstAudioStream(BaseItemDto item) { if (item.getMediaSources() == null || item.getMediaSources().size() < 1) return null; List<MediaStream> streams = GetAudioStreams(item.getMediaSources().get(0)); if (streams == null || streams.size() < 1) return null; return streams.get(0); } public static List<MediaStream> GetVideoStreams(MediaSourceInfo mediaSource) { return GetStreams(mediaSource, MediaStreamType.Video); } public static List<MediaStream> GetStreams(MediaSourceInfo mediaSource, MediaStreamType type) { ArrayList<MediaStream> streams = mediaSource.getMediaStreams(); ArrayList<MediaStream> ret = new ArrayList<>(); if (streams != null) { for (MediaStream stream : streams) { if (stream.getType() == type) { ret.add(stream); } } } return ret; } public static void ReportStopped(BaseItemDto item, StreamInfo streamInfo, long pos) { if (item != null && streamInfo != null) { PlaybackStopInfo info = new PlaybackStopInfo(); ApiClient apiClient = TvApp.getApplication().getApiClient(); info.setItemId(item.getId()); info.setPositionTicks(pos); TvApp.getApplication().getPlaybackManager().reportPlaybackStopped(info, streamInfo, apiClient.getServerInfo().getId(), TvApp.getApplication().getCurrentUser().getId(), false, apiClient, new EmptyResponse()); TvApp.getApplication().setLastPlayback(Calendar.getInstance()); switch (item.getType()) { case "Movie": TvApp.getApplication().setLastMoviePlayback(Calendar.getInstance()); break; case "Episode": TvApp.getApplication().setLastTvPlayback(Calendar.getInstance()); break; } } } public static void ReportStart(BaseItemDto item, long pos) { PlaybackStartInfo startInfo = new PlaybackStartInfo(); startInfo.setItemId(item.getId()); startInfo.setPositionTicks(pos); TvApp.getApplication().getPlaybackManager().reportPlaybackStart(startInfo, false, TvApp.getApplication().getApiClient(), new EmptyResponse()); TvApp.getApplication().getLogger().Info("Playback of " + item.getName() + " started."); } public static void EnterManualServerAddress(final Activity activity) { final EditText address = new EditText(activity); address.setHint(activity.getString(R.string.lbl_ip_hint)); address.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); new AlertDialog.Builder(activity) .setTitle(activity.getString(R.string.lbl_enter_server_address)) .setMessage(activity.getString(R.string.lbl_valid_server_address)) .setView(address) .setNegativeButton(activity.getString(R.string.lbl_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } }).setPositiveButton(activity.getString(R.string.lbl_ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String addressValue = address.getText().toString(); TvApp.getApplication().getLogger().Debug("Entered address: " + addressValue); signInToServer(TvApp.getApplication().getConnectionManager(), addressValue.indexOf(":") < 0 ? addressValue + ":8096" : addressValue, activity); } }).show(); } public static void EnterManualUser(final Activity activity) { final EditText userName = new EditText(activity); userName.setInputType(InputType.TYPE_CLASS_TEXT); new AlertDialog.Builder(activity) .setTitle(activity.getString(R.string.lbl_enter_user_name)) .setView(userName) .setNegativeButton(activity.getString(R.string.lbl_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } }).setPositiveButton(activity.getString(R.string.lbl_ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String userValue = userName.getText().toString(); TvApp.getApplication().getLogger().Debug("Entered user: " + userValue); final EditText userPw = new EditText(activity); userPw.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); new AlertDialog.Builder(activity) .setTitle(activity.getString(R.string.lbl_enter_user_pw)) .setView(userPw) .setNegativeButton(activity.getString(R.string.lbl_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } }).setPositiveButton(activity.getString(R.string.lbl_ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Utils.loginUser(userName.getText().toString(), userPw.getText().toString(), TvApp.getApplication().getLoginApiClient(), activity); } }).show(); } }).show(); } // send the tone to the "alarm" stream (classic beeps go there) with 50% volume private static ToneGenerator ToneHandler = new ToneGenerator(AudioManager.STREAM_ALARM, 50); public static void Beep() { MakeTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 200); } public static void Beep(int ms) { MakeTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, ms); } public static void ClickSound() { MakeTone(ToneGenerator.TONE_CDMA_PIP, 50); } public static void MakeTone(int type, int ms) { ToneHandler.startTone(type, ms); } public static void ReportProgress(BaseItemDto item, StreamInfo currentStreamInfo, long position, boolean isPaused) { if (item != null) { PlaybackProgressInfo info = new PlaybackProgressInfo(); ApiClient apiClient = TvApp.getApplication().getApiClient(); info.setItemId(item.getId()); info.setPositionTicks(position); info.setIsPaused(isPaused); info.setCanSeek(currentStreamInfo.getRunTimeTicks() != null && currentStreamInfo.getRunTimeTicks() > 0); info.setIsMuted(TvApp.getApplication().isAudioMuted()); info.setPlayMethod(currentStreamInfo.getPlayMethod()); TvApp.getApplication().getPlaybackManager().reportPlaybackProgress(info, currentStreamInfo, false, apiClient, new EmptyResponse()); } } public static boolean isTrue(Boolean value) { return value != null && value; } public static Date convertToLocalDate(Date utcDate) { TimeZone timeZone = Calendar.getInstance().getTimeZone(); Date convertedDate = new Date( utcDate.getTime() + timeZone.getRawOffset() ); if ( timeZone.inDaylightTime(convertedDate) ) { Date dstDate = new Date( convertedDate.getTime() + timeZone.getDSTSavings() ); if (timeZone.inDaylightTime( dstDate )) { convertedDate = dstDate; } } return convertedDate; } public static Date convertToUtcDate(Date localDate) { TimeZone timeZone = Calendar.getInstance().getTimeZone(); Date convertedDate = new Date( localDate.getTime() - timeZone.getRawOffset() ); if ( timeZone.inDaylightTime(localDate) ) { Date dstDate = new Date( convertedDate.getTime() - timeZone.getDSTSavings() ); if (timeZone.inDaylightTime( dstDate )) { convertedDate = dstDate; } } return convertedDate; } /** * Returns a pseudo-random number between min and max, inclusive. * The difference between min and max can be at most * <code>Integer.MAX_VALUE - 1</code>. * * @param min Minimum value * @param max Maximum value. Must be greater than min. * @return Integer between min and max, inclusive. * @see java.util.Random#nextInt(int) */ private static Random rand = new Random(); public static int randInt(int min, int max) { if (max <= min) return min; // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive return rand.nextInt((max - min) + 1) + min; } public static void signInToServer(IConnectionManager connectionManager, final ServerInfo server, final Activity activity) { connectionManager.Connect(server, new Response<ConnectionResult>() { @Override public void onResponse(ConnectionResult serverResult) { switch (serverResult.getState()) { case SignedIn: case ServerSignIn: //Set api client for login TvApp.getApplication().setLoginApiClient(serverResult.getApiClient()); //Open user selection Intent userIntent = new Intent(activity, SelectUserActivity.class); userIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); activity.startActivity(userIntent); break; default: TvApp.getApplication().getLogger().Error("Unexpected response " + serverResult.getState() + " trying to sign in to specific server " + server.getLocalAddress()); break; } } }); } public static void signInToServer(IConnectionManager connectionManager, String address, final Activity activity) { connectionManager.Connect(address, new Response<ConnectionResult>() { @Override public void onResponse(ConnectionResult serverResult) { switch (serverResult.getState()) { case ServerSignIn: //Set api client for login TvApp.getApplication().setLoginApiClient(serverResult.getApiClient()); //Open user selection Intent userIntent = new Intent(activity, SelectUserActivity.class); userIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); activity.startActivity(userIntent); break; default: TvApp.getApplication().getLogger().Error("Unexpected response from server login "+ serverResult.getState()); Utils.showToast(activity, activity.getString(R.string.msg_error_connecting_server)); } } @Override public void onError(Exception exception) { reportError(activity, activity.getString(R.string.msg_error_connecting_server)); } }); } public static AndroidProfileOptions getProfileOptions() { AndroidProfileOptions options = new AndroidProfileOptions(Build.MODEL); options.SupportsHls = false; options.SupportsMkv = true; // options.SupportsAc3 = is60(); // options.SupportsDts = is60(); return options; } public static String getFriendlyDate(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); Calendar now = Calendar.getInstance(); if (cal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) { if (cal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR)) return TvApp.getApplication().getString(R.string.lbl_today); if (cal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR)+1) return TvApp.getApplication().getString(R.string.lbl_tomorrow); if (cal.get(Calendar.DAY_OF_YEAR) < now.get(Calendar.DAY_OF_YEAR)+7 && cal.get(Calendar.DAY_OF_YEAR) > now.get(Calendar.DAY_OF_YEAR)) return cal.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()); } return android.text.format.DateFormat.getDateFormat(TvApp.getApplication()).format(date); } public static void reportError(final Context context, final String msg) { if (context == null) return; new AlertDialog.Builder(context) .setTitle(msg) .setMessage(context.getString(R.string.lbl_report_msg_question)) .setNegativeButton(context.getString(R.string.lbl_no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { showToast(context, context.getString(R.string.msg_report_not_sent)); } }).setPositiveButton(context.getString(R.string.lbl_yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new LogReporter().sendReport("User", null); showToast(context, context.getString(R.string.msg_report_sent)); } }).show(); } public static void loginUser(String userName, String pw, ApiClient apiClient, final Activity activity) { loginUser(userName, pw, apiClient, activity, null); } public static void loginUser(String userName, String pw, ApiClient apiClient, final Activity activity, final String directEntryItemId) { try { apiClient.AuthenticateUserAsync(userName, pw, new Response<AuthenticationResult>() { @Override public void onResponse(AuthenticationResult authenticationResult) { TvApp application = TvApp.getApplication(); application.getLogger().Debug("Signed in as " + authenticationResult.getUser().getName()); application.setCurrentUser(authenticationResult.getUser()); if (directEntryItemId == null) { Intent intent = new Intent(activity, MainActivity.class); activity.startActivity(intent); } else { Intent intent = new Intent(activity, FullDetailsActivity.class); intent.putExtra("ItemId", directEntryItemId); activity.startActivity(intent); } } @Override public void onError(Exception exception) { super.onError(exception); TvApp.getApplication().getLogger().ErrorException("Error logging in", exception); Utils.showToast(activity, activity.getString(R.string.msg_invalid_id_pw)); } }); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { e.printStackTrace(); } } public static void SaveLoginCredentials(LogonCredentials creds, String fileName) throws IOException { TvApp app = TvApp.getApplication(); OutputStream credsFile = app.openFileOutput(fileName, Context.MODE_PRIVATE); credsFile.write(app.getSerializer().SerializeToString(creds).getBytes()); credsFile.close(); app.setConfiguredAutoCredentials(creds); } public static LogonCredentials GetSavedLoginCredentials(String fileName){ TvApp app = TvApp.getApplication(); try { InputStream credsFile = app.openFileInput(fileName); String json = ReadStringFromFile(credsFile); credsFile.close(); return (LogonCredentials) app.getSerializer().DeserializeFromString(json, LogonCredentials.class); } catch (IOException e) { // none saved return new LogonCredentials(new ServerInfo(), new UserDto()); } catch (Exception e) { app.getLogger().ErrorException("Error interpreting saved login",e); return new LogonCredentials(new ServerInfo(), new UserDto()); } } public static String ReadStringFromFile(InputStream inputStream) throws IOException { StringBuffer fileContent = new StringBuffer(""); byte[] buffer = new byte[1024]; int n; while ((n = inputStream.read(buffer)) != -1) { fileContent.append(new String(buffer, 0, n)); } return fileContent.toString(); } public static String VersionString() { return TvApp.getApplication().getString(R.string.lbl_version_colon) + BuildConfig.VERSION_NAME; } public static String SafeToUpper(String value) { if (value == null) return ""; return value.toUpperCase(); } public static String FirstToUpper(String value) { if (value == null || value.length() == 0) return ""; return value.substring(0, 1).toUpperCase() + (value.length() > 1 ? value.substring(1) : ""); } public static String NullCoalesce(String obj, String def) { if (obj == null) return def; return obj; } public static int NullCoalesce(Integer obj, int def) { if (obj == null) return def; return obj; } public static Long NullCoalesce(Long obj, Long def) { if (obj == null) return def; return obj; } public static Double NullCoalesce(Double obj, Double def) { if (obj == null) return def; return obj; } public static boolean IsEmpty(String value) { return value == null || value.equals(""); } //todo replace with custom error reporter // public static void PutCustomAcraData() { // TvApp app = TvApp.getApplication(); // ApiClient apiClient = app.getApiClient(); // if (apiClient != null) { // if (app.getCurrentUser() != null) ACRA.getErrorReporter().putCustomData("mbUser", app.getCurrentUser().getName()); // ACRA.getErrorReporter().putCustomData("serverInfo", app.getSerializer().SerializeToString(app.getCurrentSystemInfo())); // } // } public static boolean versionGreaterThanOrEqual(String firstVersion, String secondVersion) { try { String[] firstVersionComponents = firstVersion.split("[.]"); String[] secondVersionComponents = secondVersion.split("[.]"); int firstLength = firstVersionComponents.length; int secondLength = secondVersionComponents.length; int firstMajor = firstLength > 0 ? Integer.parseInt(firstVersionComponents[0]) : 0; int secondMajor = secondLength > 0 ? Integer.parseInt(secondVersionComponents[0]) : 0; int firstMinor = firstLength > 1 ? Integer.parseInt(firstVersionComponents[1]) : 0; int secondMinor = secondLength > 1 ? Integer.parseInt(secondVersionComponents[1]) : 0; int firstBuild = firstLength > 2 ? Integer.parseInt(firstVersionComponents[2]) : 0; int secondBuild = secondLength > 0 ? Integer.parseInt(secondVersionComponents[2]) : 0; int firstRelease = firstLength > 3 ? Integer.parseInt(firstVersionComponents[3]) : 0; int secondRelease = secondLength > 3 ? Integer.parseInt(secondVersionComponents[3]) : 0; if (firstMajor < secondMajor) return false; if (firstMajor == secondMajor && firstMinor < secondMinor) return false; if (firstMajor == secondMajor && firstMinor == secondMinor && firstBuild < secondBuild) return false; if (firstMajor == secondMajor && firstMinor == secondMinor && firstBuild == secondBuild && firstRelease < secondRelease) return false; return true; } catch (Exception e) { return false; } } public static int getReleaseVersion(String version) { String[] components = version.split("[.]"); return components.length > 2 ? Integer.parseInt(components[2]) : 0; } public static String getCurrentFormattedTime() { String fullTime = android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(new Date()); return android.text.format.DateFormat.is24HourFormat(TvApp.getApplication()) ? fullTime : TextUtils.substring(fullTime, 0, fullTime.length()-3 ); // strip off am/pm } public static Bitmap getBitmapFromURL(String src) { try { URL url = new URL(src); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); Bitmap myBitmap = BitmapFactory.decodeStream(input); return myBitmap; } catch (IOException e) { // Log exception return null; } } public static PopupMenu createPopupMenu(Activity activity, View view, int gravity) { return new PopupMenu(activity, view, gravity); } public static boolean isFireTv() { return Build.MODEL.startsWith("AFT"); } public static boolean isFireTvStick() { return Build.MODEL.equals("AFTM"); } public static boolean isShield() { return Build.MODEL.equals("SHIELD Android TV"); } public static boolean isNexus() { return Build.MODEL.equals("Nexus Player"); } public static boolean is50() { return Build.VERSION.SDK_INT >= 21; } public static boolean isGreaterThan51() { return Build.VERSION.RELEASE.equals("5.1.1") || is60(); } public static boolean is60() { return Build.VERSION.SDK_INT >= 23; } public static boolean isGingerbreadOrLater() { return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD; } public static boolean supportsAc3() { return true; } public static int getBrandColor() { return TvApp.getApplication().getPrefs().getInt("pref_sideline_color", isFireTv() ? TvApp.getApplication().getResources().getColor(R.color.fastlane_fire) : TvApp.getApplication().getResources().getColor(R.color.fastlane_background)); } public static void processPasswordEntry(Activity activity, UserDto user) { processPasswordEntry(activity, user, null); } public static void processPasswordEntry(final Activity activity, final UserDto user, final String directItemId) { if (TvApp.getApplication().getPrefs().getBoolean("pref_alt_pw_entry", false)) { Intent pwIntent = new Intent(activity, DpadPwActivity.class); pwIntent.putExtra("User", TvApp.getApplication().getSerializer().SerializeToString(user)); pwIntent.putExtra("ItemId", directItemId); pwIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); activity.startActivity(pwIntent); } else { TvApp.getApplication().getLogger().Debug("Requesting dialog..."); final EditText password = new EditText(activity); password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); new AlertDialog.Builder(activity) .setTitle("Enter Password") .setMessage("Please enter password for " + user.getName()) .setView(password) .setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String pw = password.getText().toString(); Utils.loginUser(user.getName(), pw, TvApp.getApplication().getLoginApiClient(), activity, directItemId); } }).show(); } } private static String convertToHex(byte[] data) { StringBuilder buf = new StringBuilder(); for (byte aData : data) { int halfbyte = (aData >>> 4) & 0x0F; int two_halfs = 0; do { if ((0 <= halfbyte) && (halfbyte <= 9)) buf.append((char) ('0' + halfbyte)); else buf.append((char) ('a' + (halfbyte - 10))); halfbyte = aData & 0x0F; } while (two_halfs++ < 1); } return buf.toString(); } public static String MD5(String text) { try { MessageDigest md; md = MessageDigest.getInstance("MD5"); byte[] md5hash = new byte[32]; md.update(text.getBytes("iso-8859-1"), 0, text.length()); md5hash = md.digest(); return convertToHex(md5hash); } catch (UnsupportedEncodingException e) { return UUID.randomUUID().toString(); } catch (NoSuchAlgorithmException e) { return UUID.randomUUID().toString(); } } public static void handleConnectionResponse(final IConnectionManager connectionManager, final Activity activity, ConnectionResult response) { ILogger logger = TvApp.getApplication().getLogger(); switch (response.getState()) { case ConnectSignIn: logger.Debug("Sign in with connect..."); Intent intent = new Intent(activity, ConnectActivity.class); activity.startActivity(intent); break; case Unavailable: logger.Debug("No server available..."); Utils.showToast(activity, "No MB Servers available..."); break; case ServerSignIn: logger.Debug("Sign in with server " + response.getServers().get(0).getName() + " total: " + response.getServers().size()); Utils.signInToServer(connectionManager, response.getServers().get(0), activity); break; case SignedIn: logger.Debug("Ignoring saved connection manager sign in"); connectionManager.GetAvailableServers(new Response<ArrayList<ServerInfo>>(){ @Override public void onResponse(ArrayList<ServerInfo> serverResponse) { if (serverResponse.size() == 1) { //Signed in before and have just one server so go directly to user screen Utils.signInToServer(connectionManager, serverResponse.get(0), activity); } else { //More than one server so show selection Intent serverIntent = new Intent(activity, SelectServerActivity.class); GsonJsonSerializer serializer = TvApp.getApplication().getSerializer(); List<String> payload = new ArrayList<>(); for (ServerInfo server : serverResponse) { payload.add(serializer.SerializeToString(server)); } serverIntent.putExtra("Servers", payload.toArray(new String[payload.size()])); serverIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); activity.startActivity(serverIntent); } } }); break; case ServerSelection: logger.Debug("Select A server"); connectionManager.GetAvailableServers(new Response<ArrayList<ServerInfo>>(){ @Override public void onResponse(ArrayList<ServerInfo> serverResponse) { Intent serverIntent = new Intent(activity, SelectServerActivity.class); GsonJsonSerializer serializer = TvApp.getApplication().getSerializer(); List<String> payload = new ArrayList<>(); for (ServerInfo server : serverResponse) { payload.add(serializer.SerializeToString(server)); } serverIntent.putExtra("Servers", payload.toArray(new String[payload.size()])); serverIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); activity.startActivity(serverIntent); } }); break; } } public static boolean downMixAudio() { AudioManager am = (AudioManager) TvApp.getApplication().getSystemService(Context.AUDIO_SERVICE); if (am.isBluetoothA2dpOn()) { TvApp.getApplication().getLogger().Info("Downmixing audio due to wired headset"); return true; } return (isFireTv() && !is50()) || "1".equals(TvApp.getApplication().getPrefs().getString("pref_audio_option","0")); } }