From 47f56950a5711421fe2cbd4fedd9ef51d4845034 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 14 May 2020 19:25:16 +0100 Subject: [PATCH] url history and player fixes --- app/src/main/AndroidManifest.xml | 5 +- .../io/lbry/browser/FileViewActivity.java | 146 +++++++++++++++--- .../java/io/lbry/browser/MainActivity.java | 87 +++++++---- .../adapter/UrlSuggestionListAdapter.java | 5 +- .../io/lbry/browser/data/DatabaseHelper.java | 15 +- .../java/io/lbry/browser/model/Claim.java | 7 +- .../io/lbry/browser/model/ClaimCacheKey.java | 60 +++---- .../io/lbry/browser/model/UrlSuggestion.java | 5 + .../browser/tasks/claim/ClaimSearchTask.java | 2 +- .../lbry/browser/tasks/claim/ResolveTask.java | 2 +- .../localdata/FetchRecentUrlHistoryTask.java | 37 +++++ ...storyTask.java => SaveUrlHistoryTask.java} | 19 ++- .../tasks/localdata/SaveViewHistoryTask.java | 4 + .../browser/ui/channel/ChannelFragment.java | 20 ++- .../browser/ui/search/SearchFragment.java | 2 +- .../java/io/lbry/browser/utils/Helper.java | 26 ++++ .../main/java/io/lbry/browser/utils/Lbry.java | 18 ++- app/src/main/res/drawable-anydpi/ic_cast.xml | 11 ++ .../res/drawable-anydpi/ic_cast_connected.xml | 11 ++ app/src/main/res/drawable-hdpi/ic_cast.png | Bin 0 -> 410 bytes .../res/drawable-hdpi/ic_cast_connected.png | Bin 0 -> 477 bytes app/src/main/res/drawable-mdpi/ic_cast.png | Bin 0 -> 280 bytes .../res/drawable-mdpi/ic_cast_connected.png | Bin 0 -> 318 bytes app/src/main/res/drawable-xhdpi/ic_cast.png | Bin 0 -> 497 bytes .../res/drawable-xhdpi/ic_cast_connected.png | Bin 0 -> 563 bytes app/src/main/res/drawable-xxhdpi/ic_cast.png | Bin 0 -> 750 bytes .../res/drawable-xxhdpi/ic_cast_connected.png | Bin 0 -> 867 bytes .../main/res/layout/activity_file_view.xml | 8 +- app/src/main/res/layout/app_bar_main.xml | 1 - app/src/main/res/layout/content_main.xml | 4 +- .../res/layout/exo_playback_control_view.xml | 22 ++- app/src/main/res/values/strings.xml | 1 + 32 files changed, 395 insertions(+), 123 deletions(-) create mode 100644 app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java rename app/src/main/java/io/lbry/browser/tasks/localdata/{CreateUrlHistoryTask.java => SaveUrlHistoryTask.java} (64%) create mode 100644 app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java create mode 100644 app/src/main/res/drawable-anydpi/ic_cast.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_cast_connected.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_cast.png create mode 100644 app/src/main/res/drawable-hdpi/ic_cast_connected.png create mode 100644 app/src/main/res/drawable-mdpi/ic_cast.png create mode 100644 app/src/main/res/drawable-mdpi/ic_cast_connected.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_cast.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_cast_connected.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_cast.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_cast_connected.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bb39341..ea66be0f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,9 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> + + @@ -39,7 +42,7 @@ android:supportsPictureInPicture="true" android:theme="@style/AppTheme.NoActionBar" android:launchMode="singleTask" - android:windowSoftInputMode="adjustPan"> + android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/io/lbry/browser/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java index 5f7654cd..83a90954 100644 --- a/app/src/main/java/io/lbry/browser/FileViewActivity.java +++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java @@ -1,7 +1,6 @@ package io.lbry.browser; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.BroadcastReceiver; @@ -34,7 +33,6 @@ import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.app.NavUtils; import androidx.core.content.ContextCompat; import androidx.core.widget.NestedScrollView; import androidx.preference.PreferenceManager; @@ -43,11 +41,15 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.github.chrisbanes.photoview.PhotoView; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; @@ -84,6 +86,7 @@ import io.lbry.browser.model.ClaimCacheKey; import io.lbry.browser.model.Fee; import io.lbry.browser.model.LbryFile; import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.ReadTextFileTask; @@ -115,6 +118,8 @@ public class FileViewActivity extends AppCompatActivity { private static final int RELATED_CONTENT_SIZE = 16; private static boolean startingShareActivity; + private PlayerControlView castControlView; + private Player currentPlayer; private boolean backStackLost; private boolean loadingNewClaim; private boolean stopServiceReceived; @@ -163,11 +168,8 @@ public class FileViewActivity extends AppCompatActivity { instance = this; ClaimCacheKey key = new ClaimCacheKey(); key.setClaimId(claimId); - if (url.contains("#")) { - key.setPermanentUrl(url); // use the same url for the key so that we can match the key for any value that's the same - key.setCanonicalUrl(url); - key.setShortUrl(url); - } + key.setUrl(url); // use the same url for the key so that we can match the key for any value that's the same + if (Lbry.claimCache.containsKey(key)) { claim = Lbry.claimCache.get(key); checkAndResetNowPlayingClaim(); @@ -181,6 +183,7 @@ public class FileViewActivity extends AppCompatActivity { currentUrl = url; logUrlEvent(url); + Helper.saveUrlHistory(url, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_FILE); if (claim == null) { MainActivity.clearNowPlayingClaim(this); resolveUrl(url); @@ -218,6 +221,7 @@ public class FileViewActivity extends AppCompatActivity { } }; + castControlView = findViewById(R.id.file_view_cast_control_view); initUi(); onWalletBalanceUpdated(); renderClaim(); @@ -278,9 +282,7 @@ public class FileViewActivity extends AppCompatActivity { ClaimCacheKey key = new ClaimCacheKey(); key.setClaimId(newClaimId); if (!Helper.isNullOrEmpty(newUrl) && newUrl.contains("#")) { - key.setPermanentUrl(newUrl); - key.setCanonicalUrl(newUrl); - key.setShortUrl(newUrl); + key.setUrl(newUrl); } loadClaimForCacheKey(key, newUrl); } else if (!Helper.isNullOrEmpty(newUrl)) { @@ -290,9 +292,7 @@ public class FileViewActivity extends AppCompatActivity { onNewClaim(newUrl); ClaimCacheKey key = new ClaimCacheKey(); - key.setPermanentUrl(newUrl); - key.setCanonicalUrl(newUrl); - key.setShortUrl(newUrl); + key.setUrl(newUrl); loadClaimForCacheKey(key, newUrl); } } @@ -313,6 +313,7 @@ public class FileViewActivity extends AppCompatActivity { private void loadClaimForCacheKey(ClaimCacheKey key, String url) { if (Lbry.claimCache.containsKey(key)) { claim = Lbry.claimCache.get(key); + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE); checkAndResetNowPlayingClaim(); if (claim.getFile() == null) { loadFile(); @@ -322,6 +323,7 @@ public class FileViewActivity extends AppCompatActivity { } renderClaim(); } else { + Helper.saveUrlHistory(url, null, UrlSuggestion.TYPE_FILE); findViewById(R.id.file_view_claim_display_area).setVisibility(View.INVISIBLE); MainActivity.clearNowPlayingClaim(this); resolveUrl(url); @@ -452,6 +454,8 @@ public class FileViewActivity extends AppCompatActivity { super.onResume(); MainActivity.mainActive = false; MainActivity.startingFileViewActivity = false; + + loadAndScheduleDurations(); if (Lbry.SDK_READY) { initFloatingWalletBalance(); } @@ -468,12 +472,25 @@ public class FileViewActivity extends AppCompatActivity { claim = claims.get(0); if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { claim = claim.getRepostedClaim(); - // cache the reposted claim too for subsequent loads - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - Lbry.claimCache.put(key, claim); + Lbry.addClaimToCache(claim); + if (claim.getName().startsWith("@")) { + // this is a reposted channel, so finish this activity and launch the channel url + Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); + intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + sendBroadcast(intent); + + bringMainTaskToFront(); + finish(); + return; + } + } else { + Lbry.addClaimToCache(claim); } + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE); + + // also save view history checkAndResetNowPlayingClaim(); loadFile(); renderClaim(); @@ -644,7 +661,14 @@ public class FileViewActivity extends AppCompatActivity { } }); - findViewById(R.id.player_toggle_full_screen).setOnClickListener(new View.OnClickListener() { + findViewById(R.id.player_toggle_cast).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleCast(); + } + }); + + findViewById(R.id.player_toggle_fullscreen).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // check full screen mode @@ -664,7 +688,6 @@ public class FileViewActivity extends AppCompatActivity { Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); intent.putExtra("url", !Helper.isNullOrEmpty(publisher.getShortUrl()) ? publisher.getShortUrl() : publisher.getPermanentUrl()); sendBroadcast(intent); - bringMainTaskToFront(); finish(); } @@ -862,12 +885,12 @@ public class FileViewActivity extends AppCompatActivity { boolean newPlayerCreated = false; if (MainActivity.appPlayer == null) { MainActivity.appPlayer = new SimpleExoPlayer.Builder(this).build(); + MainActivity.castPlayer = new CastPlayer(MainActivity.castContext); newPlayerCreated = true; - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); PlayerView view = findViewById(R.id.file_view_exoplayer_view); view.setPlayer(MainActivity.appPlayer); if (MainActivity.nowPlayingClaim != null && @@ -881,6 +904,20 @@ public class FileViewActivity extends AppCompatActivity { showBuffering(); MainActivity.appPlayer.addListener(fileViewPlayerListener); + MainActivity.castPlayer.addListener(fileViewPlayerListener); + MainActivity.castPlayer.setSessionAvailabilityListener(new SessionAvailabilityListener() { + @Override + public void onCastSessionAvailable() { + setCurrentPlayer(MainActivity.castPlayer); + } + + @Override + public void onCastSessionUnavailable() { + setCurrentPlayer(MainActivity.appPlayer); + } + }); + + castControlView.setPlayer(MainActivity.castPlayer); MainActivity.setNowPlayingClaim(claim, FileViewActivity.this); String userAgent = Util.getUserAgent(this, getString(R.string.app_name)); @@ -893,6 +930,47 @@ public class FileViewActivity extends AppCompatActivity { MainActivity.appPlayer.prepare(mediaSource, true, true); } + private void setCurrentPlayer(Player currentPlayer) { + if (this.currentPlayer == currentPlayer) { + return; + } + + // View management. + if (currentPlayer == MainActivity.appPlayer) { + //localPlayerView.setVisibility(View.VISIBLE); + castControlView.hide(); + ((ImageView) findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast); + } else /* currentPlayer == castPlayer */ { + castControlView.show(); + ((ImageView) findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast_connected); + } + + // Player state management. + long playbackPositionMs = C.TIME_UNSET; + int windowIndex = C.INDEX_UNSET; + boolean playWhenReady = false; + + Player previousPlayer = this.currentPlayer; + if (previousPlayer != null) { + // Save state from the previous player. + int playbackState = previousPlayer.getPlaybackState(); + if (playbackState != Player.STATE_ENDED) { + playbackPositionMs = previousPlayer.getCurrentPosition(); + playWhenReady = previousPlayer.getPlayWhenReady(); + } + previousPlayer.stop(true); + } + + this.currentPlayer = currentPlayer; + + // Media queue management. + /*if (currentPlayer == exoPlayer) { + exoPlayer.prepare(concatenatingMediaSource); + }*/ + currentPlayer.seekTo(playbackPositionMs); + currentPlayer.setPlayWhenReady(true); + } + private void resetViewCount() { TextView textViewCount = findViewById(R.id.file_view_view_count); Helper.setViewText(textViewCount, null); @@ -1179,7 +1257,7 @@ public class FileViewActivity extends AppCompatActivity { if (claim.getName().startsWith("@")) { // opening a channel Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); - intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + intent.putExtra("url", claim.getPermanentUrl()); sendBroadcast(intent); bringMainTaskToFront(); finish(); @@ -1409,6 +1487,18 @@ public class FileViewActivity extends AppCompatActivity { Helper.setViewText(findViewById(R.id.player_duration_total), Helper.formatDuration(Double.valueOf(totalDuration / 1000.0).longValue())); } + private void loadAndScheduleDurations() { + if (MainActivity.appPlayer != null) { + if (totalDuration == 0) { + elapsedDuration = MainActivity.appPlayer.getCurrentPosition(); + totalDuration = MainActivity.appPlayer.getDuration(); + } + renderElapsedDuration(); + renderTotalDuration(); + scheduleElapsedPlayback(); + } + } + private void logPlay(String url, long startTimeMillis) { long timeToStartMillis = startTimeMillis > 0 ? System.currentTimeMillis() - startTimeMillis : 0; @@ -1554,6 +1644,19 @@ public class FileViewActivity extends AppCompatActivity { findViewById(R.id.floating_balance_main_container).setVisibility(View.VISIBLE); } + private void toggleCast() { + if (!MainActivity.castPlayer.isCastSessionAvailable()) { + showError(getString(R.string.no_cast_session_available)); + return; + } + + if (currentPlayer == MainActivity.appPlayer) { + setCurrentPlayer(MainActivity.castPlayer); + } else { + setCurrentPlayer(MainActivity.appPlayer); + } + } + private void onDownloadAborted() { downloadInProgress = false; @@ -1598,6 +1701,7 @@ public class FileViewActivity extends AppCompatActivity { final Set categories = baseIntent.getCategories(); if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { task.moveToFront(); + finish(); return; } } diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 793bf2a5..b9775219 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -36,7 +36,9 @@ import android.widget.TextView; import android.widget.Toast; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ext.cast.CastPlayer; import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.android.material.snackbar.Snackbar; @@ -105,6 +107,7 @@ import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.MergeSubscriptionsTask; import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask; import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask; import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask; @@ -140,6 +143,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private Map specialRouteFragmentClassMap; private boolean inPictureInPictureMode; public static SimpleExoPlayer appPlayer; + public static CastContext castContext; + public static CastPlayer castPlayer; public static Claim nowPlayingClaim; public static boolean startingFilePickerActivity = false; public static boolean startingShareActivity = false; @@ -225,7 +230,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private NavigationMenuAdapter navMenuAdapter; private UrlSuggestionListAdapter urlSuggestionListAdapter; - private List recentHistory; + private List recentUrlHistory; private boolean hasLoadedFirstBalance; // broadcast receivers @@ -295,7 +300,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener initKeyStore(); loadAuthToken(); - dbHelper = new DatabaseHelper(this); if (!isDarkMode()) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } @@ -315,6 +319,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }); super.onCreate(savedInstanceState); + castContext = CastContext.getSharedInstance(this); + dbHelper = new DatabaseHelper(this); checkNotificationOpenIntent(getIntent()); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); @@ -542,9 +548,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private Claim getCachedClaimForUrl(String url) { ClaimCacheKey key = new ClaimCacheKey(); - key.setCanonicalUrl(url); - key.setPermanentUrl(url); - key.setShortUrl(url); + key.setUrl(url); return Lbry.claimCache.containsKey(key) ? Lbry.claimCache.get(key) : null; } @@ -881,19 +885,25 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener for (int i = 0; i < claims.size(); i++) { // build a simple url from the claim for matching Claim claim = claims.get(i); + Claim actualClaim = claim; + boolean isRepost = false; + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { + actualClaim = claim.getRepostedClaim(); + isRepost = true; + } if (Helper.isNullOrEmpty(claim.getName())) { continue; } LbryUri simpleUrl = new LbryUri(); - if (claim.getName().startsWith("@")) { + if (actualClaim.getName().startsWith("@") && !isRepost) { // channel - simpleUrl.setChannelName(claim.getName()); + simpleUrl.setChannelName(actualClaim.getName()); } else { simpleUrl.setStreamName(claim.getName()); } - urlSuggestionListAdapter.setClaimForUrl(simpleUrl, claim); + urlSuggestionListAdapter.setClaimForUrl(simpleUrl, actualClaim); } urlSuggestionListAdapter.notifyDataSetChanged(); } @@ -909,10 +919,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private void displayUrlSuggestionsForNoInput() { urlSuggestionListAdapter.clear(); - List blankSuggestions = buildDefaultSuggestionsForBlankUrl(); - urlSuggestionListAdapter.addUrlSuggestions(blankSuggestions); - List urls = urlSuggestionListAdapter.getItemUrls(); - resolveUrlSuggestions(urls); + loadDefaultSuggestionsForBlankUrl(); } private void handleUriInputChanged(String text) { @@ -948,25 +955,48 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private List buildDefaultSuggestionsForBlankUrl() { - List suggestions = new ArrayList<>(); - if (recentHistory != null && recentHistory.size() > 0) { - // show recent history if avaiable - suggestions = new ArrayList<>(recentHistory); - } else { - try { - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_FILE, "What is LBRY?", LbryUri.parse("lbry://what#19b9c243bea0c45175e6a6027911abbad53e983e"))); - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_CHANNEL, "LBRYCast", LbryUri.parse("lbry://@lbrycast#4c29f8b013adea4d5cca1861fb2161d5089613ea"))); - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_CHANNEL, "The LBRY Channel", LbryUri.parse("lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a"))); + private void loadDefaultSuggestionsForBlankUrl() { + if (recentUrlHistory != null && recentUrlHistory.size() > 0) { + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + } + + FetchRecentUrlHistoryTask task = new FetchRecentUrlHistoryTask(DatabaseHelper.getInstance(), new FetchRecentUrlHistoryTask.FetchRecentUrlHistoryHandler() { + @Override + public void onSuccess(List recentHistory) { + List suggestions = new ArrayList<>(recentHistory); + List lbrySuggestions = buildLbryUrlSuggestions(); + if (suggestions.size() < 10) { + for (int i = suggestions.size(), j = 0; i < 10 && j < lbrySuggestions.size(); i++, j++) { + suggestions.add(lbrySuggestions.get(j)); + } + } else if (suggestions.size() == 0) { + suggestions.addAll(lbrySuggestions); + } + for (UrlSuggestion suggestion : suggestions) { suggestion.setUseTextAsDescription(true); } - } catch (LbryUriException ex) { - // pass + + recentUrlHistory = new ArrayList<>(suggestions); + urlSuggestionListAdapter.clear(); + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private List buildLbryUrlSuggestions() { + List suggestions = new ArrayList<>(); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_FILE, "What is LBRY?", LbryUri.tryParse("lbry://what#19b9c243bea0c45175e6a6027911abbad53e983e"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "LBRYCast", LbryUri.tryParse("lbry://@lbrycast#4c29f8b013adea4d5cca1861fb2161d5089613ea"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "The LBRY Channel", LbryUri.tryParse("lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a"))); + for (UrlSuggestion suggestion : suggestions) { + suggestion.setUseTextAsDescription(true); } return suggestions; } @@ -975,7 +1005,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener List suggestions = new ArrayList(); if (LbryUri.PROTO_DEFAULT.equalsIgnoreCase(text)) { - return buildDefaultSuggestionsForBlankUrl(); + loadDefaultSuggestionsForBlankUrl(); + return recentUrlHistory != null ? recentUrlHistory : new ArrayList<>(); } // First item is always search diff --git a/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java index 1ebf542e..cc2a09ea 100644 --- a/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java @@ -16,6 +16,7 @@ import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.LbryUri; import lombok.Setter; @@ -96,7 +97,7 @@ public class UrlSuggestionListAdapter extends RecyclerView.Adapter> { } protected List doInBackground(Void... params) { try { - return Lbry.claimSearch(options, connectionString); + return Helper.filterInvalidReposts(Lbry.claimSearch(options, connectionString)); } catch (ApiCallException ex) { error = ex; return null; diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java index 5cab8c93..120c36b7 100644 --- a/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java @@ -34,7 +34,7 @@ public class ResolveTask extends AsyncTask> { } protected List doInBackground(Void... params) { try { - return Lbry.resolve(urls, connectionString); + return Helper.filterInvalidReposts(Lbry.resolve(urls, connectionString)); } catch (ApiCallException ex) { error = ex; return null; diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java new file mode 100644 index 00000000..852e87ae --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java @@ -0,0 +1,37 @@ +package io.lbry.browser.tasks.localdata; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.UrlSuggestion; + +public class FetchRecentUrlHistoryTask extends AsyncTask> { + private DatabaseHelper dbHelper; + private FetchRecentUrlHistoryHandler handler; + public FetchRecentUrlHistoryTask(DatabaseHelper dbHelper, FetchRecentUrlHistoryHandler handler) { + this.dbHelper = dbHelper; + this.handler = handler; + } + protected List doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + return DatabaseHelper.getRecentHistory(db); + } catch (SQLiteException ex) { + return new ArrayList<>(); + } + } + protected void onPostExecute(List recentHistory) { + if (handler != null) { + handler.onSuccess(recentHistory); + } + } + + public interface FetchRecentUrlHistoryHandler { + void onSuccess(List recentHistory); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java similarity index 64% rename from app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java rename to app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java index f74683eb..a5778984 100644 --- a/app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java @@ -9,21 +9,21 @@ import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.tasks.GenericTaskHandler; -public class CreateUrlHistoryTask extends AsyncTask { - private Context context; +public class SaveUrlHistoryTask extends AsyncTask { + private DatabaseHelper dbHelper; private UrlSuggestion suggestion; - private GenericTaskHandler handler; + private SaveUrlHistoryHandler handler; private Exception error; - public CreateUrlHistoryTask(UrlSuggestion suggestion, Context context, GenericTaskHandler handler) { + public SaveUrlHistoryTask(UrlSuggestion suggestion, DatabaseHelper dbHelper, SaveUrlHistoryHandler handler) { this.suggestion = suggestion; - this.context = context; + this.dbHelper = dbHelper; this.handler = handler; } protected Boolean doInBackground(Void... params) { try { - SQLiteDatabase db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + SQLiteDatabase db = dbHelper.getWritableDatabase(); DatabaseHelper.createOrUpdateUrlHistoryItem(suggestion.getText(), suggestion.getUri().toString(), suggestion.getType(), db); } catch (Exception ex) { error = ex; @@ -35,10 +35,15 @@ public class CreateUrlHistoryTask extends AsyncTask { protected void onPostExecute(Boolean result) { if (handler != null) { if (result) { - handler.onSuccess(); + handler.onSuccess(suggestion); } else { handler.onError(error); } } } + + public interface SaveUrlHistoryHandler { + void onSuccess(UrlSuggestion item); + void onError(Exception error); + } } diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java new file mode 100644 index 00000000..097d5a13 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java @@ -0,0 +1,4 @@ +package io.lbry.browser.tasks.localdata; + +public class SaveViewHistoryTask { +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java index d08d2539..75d2a41b 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java @@ -31,10 +31,13 @@ import java.util.Map; import io.lbry.browser.MainActivity; import io.lbry.browser.R; +import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.dialog.SendTipDialogFragment; import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.listener.FetchChannelsListener; import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; import io.lbry.browser.tasks.claim.ClaimListResultHandler; @@ -296,13 +299,24 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen if (updateRequired) { resetSubCount(); if (!Helper.isNullOrEmpty(url)) { - resolveUrl(); + // check if the claim is already cached + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(url); + if (Lbry.claimCache.containsKey(key)) { + claim = Lbry.claimCache.get(key); + } else { + resolveUrl(); + } } else if (claim == null) { // nothing at this location renderNothingAtLocation(); } } + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_CHANNEL); + } + if (claim != null) { renderClaim(); } @@ -315,6 +329,10 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen public void onSuccess(List claims) { if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { claim = claims.get(0); + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_CHANNEL); + } + renderClaim(); checkOwnChannel(); } else { diff --git a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java index feeb6545..0b148416 100644 --- a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java @@ -141,7 +141,7 @@ public class SearchFragment extends BaseFragment implements private void resolveFeaturedItem(String vanityUrl) { final ClaimCacheKey key = new ClaimCacheKey(); - key.setVanityUrl(vanityUrl); + key.setUrl(vanityUrl); if (Lbry.claimCache.containsKey(key)) { Claim cachedClaim = Lbry.claimCache.get(key); updateFeaturedItemFromResolvedClaim(cachedClaim); diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index 18673b39..4a7165d1 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.ShapeDrawable; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; @@ -40,10 +41,13 @@ import java.util.Map; import java.util.Random; import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.dialog.ContentFromDialogFragment; import io.lbry.browser.dialog.ContentSortDialogFragment; import io.lbry.browser.model.Claim; import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.tasks.localdata.SaveUrlHistoryTask; import okhttp3.MediaType; public final class Helper { @@ -555,4 +559,26 @@ public final class Helper { textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setText(HtmlCompat.fromHtml(textView.getText().toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)); } + + public static List filterInvalidReposts(List claims) { + List filtered = new ArrayList<>(); + for (Claim claim : claims) { + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType()) && claim.getRepostedClaim() == null) { + continue; + } + filtered.add(claim); + } + return filtered; + } + + public static void saveUrlHistory(String url, String title, int type) { + DatabaseHelper dbHelper = DatabaseHelper.getInstance(); + if (dbHelper != null) { + UrlSuggestion suggestion = new UrlSuggestion(); + suggestion.setUri(LbryUri.tryParse(url)); + suggestion.setType(type); + suggestion.setText(Helper.isNull(title) ? "" : title); + new SaveUrlHistoryTask(suggestion, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } } diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java index 3b793d6d..0982ff1d 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -250,9 +250,7 @@ public final class Lbry { Claim claim = Claim.fromJSONObject(result.getJSONObject(keys.next())); claims.add(claim); - // TODO: Add/remove/prune entries claim cache management - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - claimCache.put(key, claim); + addClaimToCache(claim); } } } catch (LbryRequestException | LbryResponseException | JSONException ex) { @@ -396,8 +394,7 @@ public final class Lbry { Claim claim = Claim.fromJSONObject(items.getJSONObject(i)); claims.add(claim); - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - claimCache.put(key, claim); + addClaimToCache(claim); } } @@ -452,4 +449,15 @@ public final class Lbry { } } } + + public static void addClaimToCache(Claim claim) { + ClaimCacheKey fullKey = ClaimCacheKey.fromClaim(claim); + ClaimCacheKey shortUrlKey = ClaimCacheKey.fromClaimShortUrl(claim); + ClaimCacheKey permanentUrlKey = ClaimCacheKey.fromClaimPermanentUrl(claim); + claimCache.put(fullKey, claim); + claimCache.put(permanentUrlKey, claim); + if (!Helper.isNullOrEmpty(shortUrlKey.getUrl())) { + claimCache.put(shortUrlKey, claim); + } + } } diff --git a/app/src/main/res/drawable-anydpi/ic_cast.xml b/app/src/main/res/drawable-anydpi/ic_cast.xml new file mode 100644 index 00000000..da0e952f --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_cast_connected.xml b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml new file mode 100644 index 00000000..491bf6c8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_cast.png b/app/src/main/res/drawable-hdpi/ic_cast.png new file mode 100644 index 0000000000000000000000000000000000000000..5449b8f5b798e6543b753855d822a708856f7408 GIT binary patch literal 410 zcmV;L0cHM)P)QvNC2vWLs=x$4d=+K`~C`PDGbdd@iLKYNJVe0V6rnGOw zf?_Jf4n1q$*9+P^K^4CDyr8W-Gy^G^fIevMim90o&;!A)2x4JvryfG$6kLKXcmO{J zVMCkUTFcZ}a0mwAOGG_`@jHsXpK?xf=V1hjk-VqQY;Wx&QzG07*qoM6N<$ Ef&qQEo&W#< literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_cast_connected.png b/app/src/main/res/drawable-hdpi/ic_cast_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..55bddaada29d60d4371625b3f36fe6a485ca1620 GIT binary patch literal 477 zcmV<30V4j1P)z@{Q54z00j?q9z3enGL$FvALseuJYsO|l4mqX< zQ#Pf29Tw!0!!=m5>V5x>ApCAd)_>3oIe5o;-kYK`g8(L>++-Z8ZeLAvgmaa1VYIi`{Xw5>bJX zqQ-=rzM5?E&%U;1v{M3r>isyu!KH;C~E^1GTcUcRJL~24IswxV-OTM<2qK5MXe2Pfb$UopHiErO^?&C*_ zsC^;ofqc8-J#}340WCgLza`%=OGGN9)YZwywREZT#P}>kjmURP_72&{ij4|8GXX T!;DXP00000NkvXXu0mjfZg}1CrGd!Y54#D|7*sN zDhhEvsys&C{5P9ALri4a)Rm$c#Fiv=ESP2fMR$SYqJ^7T9nR$_ykmQ0qp*!RDR0&h zMxl9}N9r8Tc0khu_f89kT4er zSN_>DHLB2nyTf;)PUFub{g*5*usl*)@kB|*+hl^yvt<^13s$nHDd_A-5X^U9$FIg* zC?T-f;itiT7U!RR0d;mu3kCR|W;^{{8?aAh%KcXL_X2wB96g0J{T%+>yz#>&q9N*G c4^s^TgS|vk6GvKsAJD4|p00i_>zopr0H5t;nE(I) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_cast_connected.png b/app/src/main/res/drawable-mdpi/ic_cast_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b72ba362113364ae41c1cd6cd5312f996d10d5 GIT binary patch literal 318 zcmV-E0m1%>P)_z;{`3_73(CSb!J+5wB!3_fM@O_OaZnfNL#R<7DT zh(G|kG-rY>%S4nbRPDhPjG2kZ#Am0gRu3Gorr@2K*v#0h`c9;JK&MA+D9R-o@7293 zPGAmNq(vqH=_%=z(p>}=q#e=`=|1Vh7xz_MW{|L+gh QQUCw|07*qoM6N<$f>&XOxBvhE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_cast.png b/app/src/main/res/drawable-xhdpi/ic_cast.png new file mode 100644 index 0000000000000000000000000000000000000000..fdde81a4f2a4185cc40e09398ecaae5bf3b911c9 GIT binary patch literal 497 zcmV5XPeju}-0aO&W^}lFCPjU>OU$psDSYiy)*?gkYL0d{hZ|A%XbFA9{WZE1bjZ zYD_M-3p4PmX6~5pZs*(Ou27JWkdSaDk|ZgC5DdW7F$TJ~lCAmJ!Nw^uD?+QAdP2wv z=zAof&k6WZR$z=KnQ2oM3w7-DtedjuWO0##7-(uGn?KwF9NRX~~u z?D;I!+Pv(9_7J4PZlY}l}2Ow4ApI4FZ2m>Nb;%q^~(eBZ!{Q~Iogu$zX2 zGAGatkwBLd_>*K{eX8eY1+0M20=f(J{|PMn6NXXX0yv=+@LXsg)I%%axnK&kOe3HmFZohO;4lwrOn^tVwY zUow+>VV*FRt!VwIUqFIBrDKwlX5M0BI!`4=;>qSglo_W_*kOA9K(&l?9;>wJ39zUM zTG$Dab4SKYft?Epi73gblAIzLKPBTepH4Tri|zw?l9MCj&vSNN$0vdEq5@5lvoog- z==g^Z0@6P?xa1MD*)20ZUZH^cFD4r{Y}g1SegL#4#raXA?S}vW002ovPDHLkV1nF2 B_;~;T literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast.png b/app/src/main/res/drawable-xxhdpi/ic_cast.png new file mode 100644 index 0000000000000000000000000000000000000000..62fc53f7d77dc514e5414f05b14e664cb3914801 GIT binary patch literal 750 zcmVk!u~@Klfv;eK=!d6h%=KMNt&xtI=rGf_v~=xPXU|x{xuJ zV{Q4u8He9T3`KJ}=5e2yvz{QF%IoG#lBqUK+&v4>fPh8=3s6Boqrw7In-uYB-E6!B zL_h>YKmxi6DG!8zQ8&k&I9 zukcdHGwZkkRsIe4;Yk9r{Re&scL>P6^k$ot{7|-bkT?ZlIB7EPYt=P7f8-DiGxKGN01$3aTc;T@pwT*lY@=`RFG#+rz zvFw(O+}t?P&c@Wu&l!;Rv^q&{0kphsRK1wYB(<0{^464`JcLuMkXJi>pZ}<_C+%x( ziCntXzETRY4q9`ZOxDJm8>gbS%(Bn`mEI{kdL!q7k*A$#+8y^?sFn1NHj^APVp_>r zQQ{Z>ag~2EpCVMt&B%MxWWe&aq%Qv1DF8G$N-x$`^5&KJRipK&i+^_t0QHUnC~r96 z4JCd;iQoLsfSe!HxgPLQ$(zzTeI@>UllT(?ABp*AqN1*nVPRpMJs;*Swf^N)Hd gilQirqA04=UxP?!h!53t&j0`b07*qoM6N<$f_H&uApigX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png b/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..a28df6de442f27e343dc48b1d8436ab67cac546a GIT binary patch literal 867 zcmV-p1DyPcP)%MQJ@inkl_;BwqG)^QrBMS$Vo@9L2mW9s z+Vl^N--QVV+4tscnykB%_uzZ#d()jyW@p};na$ z4zJen`m2BxkOERb3dkWqSy{RjkOE2xpi#ZU3h3`YI8;D9pmWd~bfAFju&zPR6p$U% z6=+WZ*+E^0-nzKVe}>v-kM=3F4lP065Rgtln^4#3JZM1gAs;G3Q!zl540PuK4eArL zAst!pn}v2J2~c0Zpc*tUfHVy~m^46r{f6#Bc>$=kBo=_|=0QKqq4s&uv1-``$aJE(3tbXI;zrUU6T6r7OgbDL zTY^Mz)sk`NnN7E@3#CRlUlc8nxQY876ZhPeTI?%y)v~&MU<%xMW{*2{fb!u3o4jTP zdLbO@KP`|Jn09JJ%+#n(OU9Z9A>fS3Pdb7`u*kIYnAuPA##ziJRjhh~W1SY0<9H0@ z4I-v6io?l7GikwQ*A^4&Gb`G3>aV}VXn2`{bV%h%W*2~pD?0;7FhA@vadjfb&%y<& zz#Q$=B)=##Cv~Z^#B91n9w}wzg#R4j-~P{ltRK|b8hA&<6{$*}@IMvDKOsP(Pcny^z|1Z{lz)fW tdlvhT5m5Y(dezj_)YR0})YQ~M`vXF=o}fo)a$W!c002ovPDHLkV1n4skevVk literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_file_view.xml b/app/src/main/res/layout/activity_file_view.xml index 61767607..6afd90ef 100644 --- a/app/src/main/res/layout/activity_file_view.xml +++ b/app/src/main/res/layout/activity_file_view.xml @@ -162,9 +162,15 @@ android:layout_height="wrap_content" /> - + + - diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 7a78d484..b147284d 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -20,9 +20,9 @@ android:id="@+id/url_suggestions_container" android:background="@color/pageBackground" android:elevation="6dp" + android:visibility="gone" android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone"> + android:layout_height="match_parent"> + + + + Delete file Are you sure you want to remove this file from your device? Failed to load %1$s. Please try again later. + There is no cast session available at this time. %1$s view %1$s views