implement asynchronous login to prevent several common crashes

This commit is contained in:
dkanada 2021-04-27 12:22:42 +09:00
commit 9f40b7281c
13 changed files with 235 additions and 53 deletions

View file

@ -14,6 +14,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.drawerlayout.widget.DrawerLayout;
import com.dkanada.gramophone.activities.base.AbsMusicContentActivity;
import com.dkanada.gramophone.util.NavigationUtil;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
@ -26,7 +27,6 @@ import com.dkanada.gramophone.R;
import com.dkanada.gramophone.glide.CustomGlideRequest;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsMusicPanelActivity;
import com.dkanada.gramophone.fragments.mainactivity.library.LibraryFragment;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.QueryUtil;
@ -36,7 +36,7 @@ import org.jellyfin.apiclient.model.dto.BaseItemDto;
import java.util.List;
public class MainActivity extends AbsMusicPanelActivity {
public class MainActivity extends AbsMusicContentActivity {
private ActivityMainDrawerLayoutBinding binding;
private ActivityMainContentBinding contentBinding;
private NavigationDrawerHeaderBinding navigationBinding;
@ -48,15 +48,22 @@ public class MainActivity extends AbsMusicPanelActivity {
@Nullable
private List<BaseItemDto> libraries;
@Nullable
private Bundle state;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDrawUnderStatusBar();
state = savedInstanceState;
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
binding.navigationView.setFitsSystemWindows(false);
}
}
@Override
public void onStateOnline() {
Menu menu = binding.navigationView.getMenu();
QueryUtil.getLibraries(media -> {
libraries = media;
@ -84,7 +91,7 @@ public class MainActivity extends AbsMusicPanelActivity {
setUpDrawerLayout();
menu.getItem(0).setChecked(true);
if (savedInstanceState == null) {
if (state == null) {
setCurrentFragment(LibraryFragment.newInstance());
} else {
restoreCurrentFragment();

View file

@ -1,19 +1,18 @@
package com.dkanada.gramophone.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
import com.dkanada.gramophone.model.User;
import com.dkanada.gramophone.service.LoginService;
import com.dkanada.gramophone.util.NavigationUtil;
import com.dkanada.gramophone.util.PreferenceUtil;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.session.ClientCapabilities;
import org.jellyfin.apiclient.model.system.SystemInfo;
import java.util.List;
public class SplashActivity extends AbsBaseActivity {
@Override
@ -31,40 +30,23 @@ public class SplashActivity extends AbsBaseActivity {
@Override
public void onPause() {
super.onPause();
overridePendingTransition(0, R.anim.fade_quick);
overridePendingTransition(0, R.anim.fade_delay);
}
@Override
protected void onResume() {
super.onResume();
Context context = this;
User user = App.getDatabase().userDao().getUser(PreferenceUtil.getInstance(this).getUser());
List<User> available = App.getDatabase().userDao().getUsers();
if (user == null) {
if (user == null && available.size() != 0) {
NavigationUtil.startSelect(this);
} else if (user == null) {
NavigationUtil.startLogin(this);
return;
} else {
startService(new Intent(this, LoginService.class));
new Handler().postDelayed(() -> NavigationUtil.startMain(this), 1000);
}
App.getApiClient().ChangeServerLocation(user.server);
App.getApiClient().SetAuthenticationInfo(user.token, user.id);
App.getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
ClientCapabilities clientCapabilities = new ClientCapabilities();
clientCapabilities.setSupportsMediaControl(true);
clientCapabilities.setSupportsPersistentIdentifier(true);
App.getApiClient().ensureWebSocket();
App.getApiClient().ReportCapabilities(clientCapabilities, new EmptyResponse());
NavigationUtil.startMain(context);
}
@Override
public void onError(Exception exception) {
NavigationUtil.startLogin(context);
}
});
}
}

View file

@ -0,0 +1,75 @@
package com.dkanada.gramophone.activities.base;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.interfaces.StateListener;
import com.dkanada.gramophone.service.LoginService;
import com.dkanada.gramophone.util.NavigationUtil;
public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity implements StateListener {
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, @NonNull Intent intent) {
if (intent.getAction() == null) return;
switch(intent.getAction()) {
case LoginService.STATE_ONLINE:
onStateOnline();
break;
case LoginService.STATE_OFFLINE:
NavigationUtil.startLogin(context);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final IntentFilter filter = new IntentFilter();
filter.addAction(LoginService.STATE_POLLING);
filter.addAction(LoginService.STATE_ONLINE);
filter.addAction(LoginService.STATE_OFFLINE);
registerReceiver(receiver, filter);
if (App.getApiClient() == null) {
startService(new Intent(this, LoginService.class));
} else {
onStateOnline();
}
}
@Override
protected void onResume() {
super.onResume();
if (App.getApiClient() == null) {
startService(new Intent(this, LoginService.class));
}
}
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
@Override
public void onStatePolling() {
}
@Override
public void onStateOffline() {
}
}

View file

@ -1,7 +1,6 @@
package com.dkanada.gramophone.activities.base;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
@ -15,11 +14,9 @@ import androidx.annotation.RequiresApi;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.databinding.SlidingMusicPanelLayoutBinding;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.activities.SplashActivity;
import com.dkanada.gramophone.fragments.player.AbsPlayerFragment;
import com.dkanada.gramophone.fragments.player.MiniPlayerFragment;
import com.dkanada.gramophone.fragments.player.NowPlayingScreen;
@ -46,15 +43,6 @@ public abstract class AbsMusicPanelActivity extends AbsMusicServiceActivity impl
super.onCreate(savedInstanceState);
setContentView(createContentView());
// TODO use a fragment for the splash activity
if (App.getApiClient() == null) {
Intent intent = new Intent(this, SplashActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
finish();
return;
}
currentNowPlayingScreen = PreferenceUtil.getInstance(this).getNowPlayingScreen();
// must implement AbsPlayerFragment

View file

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.util.DialogUtils;
import com.dkanada.gramophone.BuildConfig;
import com.dkanada.gramophone.activities.base.AbsMusicContentActivity;
import com.dkanada.gramophone.databinding.ActivityAlbumDetailBinding;
import com.google.android.material.appbar.AppBarLayout;
import com.kabouzeid.appthemehelper.util.ColorUtil;
@ -29,7 +30,6 @@ import com.dkanada.gramophone.interfaces.PaletteColorHolder;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsMusicPanelActivity;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.NavigationUtil;
import com.dkanada.gramophone.util.ThemeUtil;
@ -39,7 +39,7 @@ import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.List;
public class AlbumDetailActivity extends AbsMusicPanelActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public class AlbumDetailActivity extends AbsMusicContentActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public static final String EXTRA_ALBUM = BuildConfig.APPLICATION_ID + ".extra.album";
private ActivityAlbumDetailBinding binding;
@ -64,7 +64,10 @@ public class AlbumDetailActivity extends AbsMusicPanelActivity implements Palett
loadAlbumCover(album);
setAlbum(album);
}
@Override
public void onStateOnline() {
ItemQuery query = new ItemQuery();
query.setParentId(album.id);
query.setSortBy(new String[]{"ParentIndexNumber", "IndexNumber"});

View file

@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.util.DialogUtils;
import com.dkanada.gramophone.BuildConfig;
import com.dkanada.gramophone.activities.base.AbsMusicContentActivity;
import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.databinding.ActivityArtistDetailBinding;
import com.google.android.material.appbar.AppBarLayout;
@ -30,7 +31,6 @@ import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsMusicPanelActivity;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.PreferenceUtil;
@ -40,7 +40,7 @@ import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.List;
public class ArtistDetailActivity extends AbsMusicPanelActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public class ArtistDetailActivity extends AbsMusicContentActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public static final String EXTRA_ARTIST = BuildConfig.APPLICATION_ID + ".extra.artist";
private ActivityArtistDetailBinding binding;
@ -69,7 +69,10 @@ public class ArtistDetailActivity extends AbsMusicPanelActivity implements Palet
loadArtistImage(artist);
setArtist(artist);
}
@Override
public void onStateOnline() {
ItemQuery albums = new ItemQuery();
albums.setArtistIds(new String[]{artist.id});

View file

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.dkanada.gramophone.BuildConfig;
import com.dkanada.gramophone.activities.base.AbsMusicContentActivity;
import com.dkanada.gramophone.databinding.ActivityGenreDetailBinding;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.R;
@ -18,7 +19,6 @@ import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.model.Genre;
import com.dkanada.gramophone.activities.base.AbsMusicPanelActivity;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.QueryUtil;
import com.dkanada.gramophone.util.ViewUtil;
@ -28,7 +28,7 @@ import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.ArrayList;
public class GenreDetailActivity extends AbsMusicPanelActivity implements CabHolder {
public class GenreDetailActivity extends AbsMusicContentActivity implements CabHolder {
public static final String EXTRA_GENRE = BuildConfig.APPLICATION_ID + ".extra.genre";
private ActivityGenreDetailBinding binding;
@ -52,7 +52,10 @@ public class GenreDetailActivity extends AbsMusicPanelActivity implements CabHol
setUpRecyclerView();
setUpToolBar();
}
@Override
public void onStateOnline() {
ItemQuery query = new ItemQuery();
query.setGenreIds(new String[]{genre.id});

View file

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.dkanada.gramophone.BuildConfig;
import com.dkanada.gramophone.activities.base.AbsMusicContentActivity;
import com.dkanada.gramophone.databinding.ActivityPlaylistDetailBinding;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator;
@ -26,7 +27,6 @@ import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.model.Playlist;
import com.dkanada.gramophone.model.PlaylistSong;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsMusicPanelActivity;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.PlaylistUtil;
import com.dkanada.gramophone.util.ViewUtil;
@ -35,7 +35,7 @@ import org.jellyfin.apiclient.model.playlists.PlaylistItemQuery;
import java.util.ArrayList;
public class PlaylistDetailActivity extends AbsMusicPanelActivity implements CabHolder {
public class PlaylistDetailActivity extends AbsMusicContentActivity implements CabHolder {
public static String EXTRA_PLAYLIST = BuildConfig.APPLICATION_ID + ".extra.playlist";
private ActivityPlaylistDetailBinding binding;
@ -62,7 +62,10 @@ public class PlaylistDetailActivity extends AbsMusicPanelActivity implements Cab
setUpRecyclerView();
setUpToolbar();
}
@Override
public void onStateOnline() {
PlaylistItemQuery query = new PlaylistItemQuery();
query.setId(playlist.id);

View file

@ -0,0 +1,9 @@
package com.dkanada.gramophone.interfaces;
public interface StateListener {
void onStatePolling();
void onStateOnline();
void onStateOffline();
}

View file

@ -0,0 +1,73 @@
package com.dkanada.gramophone.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.BuildConfig;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.model.User;
import com.dkanada.gramophone.util.PreferenceUtil;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.session.ClientCapabilities;
import org.jellyfin.apiclient.model.system.SystemInfo;
public class LoginService extends Service {
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
public static final String STATE_POLLING = PACKAGE_NAME + ".unknown";
public static final String STATE_ONLINE = PACKAGE_NAME + ".online";
public static final String STATE_OFFLINE = PACKAGE_NAME + ".offline";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
sendBroadcast(new Intent(STATE_POLLING));
authenticate();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void authenticate() {
User user = App.getDatabase().userDao().getUser(PreferenceUtil.getInstance(this).getUser());
Context context = this;
if (user == null) {
Toast.makeText(this, context.getResources().getString(R.string.error_unexpected), Toast.LENGTH_SHORT).show();
return;
}
App.getApiClient().ChangeServerLocation(user.server);
App.getApiClient().SetAuthenticationInfo(user.token, user.id);
App.getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
ClientCapabilities clientCapabilities = new ClientCapabilities();
clientCapabilities.setSupportsMediaControl(true);
clientCapabilities.setSupportsPersistentIdentifier(true);
App.getApiClient().ensureWebSocket();
App.getApiClient().ReportCapabilities(clientCapabilities, new EmptyResponse());
sendBroadcast(new Intent(STATE_ONLINE));
}
@Override
public void onError(Exception exception) {
sendBroadcast(new Intent(STATE_OFFLINE));
}
});
}
}

View file

@ -0,0 +1,24 @@
package com.dkanada.gramophone.service.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.dkanada.gramophone.service.LoginService;
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
// network info will be null in airplane mode
if (netInfo != null && netInfo.isConnected()) {
context.sendBroadcast(new Intent(LoginService.STATE_ONLINE));
} else {
context.sendBroadcast(new Intent(LoginService.STATE_OFFLINE));
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.core.util.Pair;
import com.dkanada.gramophone.activities.LoginActivity;
import com.dkanada.gramophone.activities.MainActivity;
import com.dkanada.gramophone.activities.SelectActivity;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Genre;
@ -48,6 +49,13 @@ public class NavigationUtil {
context.startActivity(intent);
}
public static void startSelect(@NonNull final Context context) {
final Intent intent = new Intent(context, SelectActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
public static void startMain(@NonNull final Context context) {
final Intent intent = new Intent(context, MainActivity.class);