package tv.emby.embyatv;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.format.DateUtils;
import android.util.Log;
import mediabrowser.apiinteraction.ApiClient;
import mediabrowser.apiinteraction.EmptyResponse;
import mediabrowser.apiinteraction.IConnectionManager;
import mediabrowser.apiinteraction.Response;
import mediabrowser.apiinteraction.android.GsonJsonSerializer;
import mediabrowser.apiinteraction.android.VolleyHttpClient;
import mediabrowser.apiinteraction.playback.PlaybackManager;
import mediabrowser.logging.ConsoleLogger;
import mediabrowser.model.dto.BaseItemDto;
import mediabrowser.model.dto.UserDto;
import mediabrowser.model.entities.DisplayPreferences;
import mediabrowser.model.logging.ILogger;
import mediabrowser.model.registration.RegistrationInfo;
import mediabrowser.model.system.SystemInfo;
import tv.emby.embyatv.base.BaseActivity;
import tv.emby.embyatv.playback.MediaManager;
import tv.emby.embyatv.playback.PlaybackController;
import tv.emby.embyatv.playback.PlaybackOverlayActivity;
import tv.emby.embyatv.search.SearchActivity;
import tv.emby.embyatv.startup.LogonCredentials;
import tv.emby.embyatv.util.LogReporter;
import tv.emby.embyatv.util.Utils;
import tv.emby.embyatv.validation.AppValidator;
import java.util.Calendar;
import java.util.HashMap;
import android.Manifest;
/**
* Created by Eric on 11/24/2014.
*/
public class TvApp extends Application implements ActivityCompat.OnRequestPermissionsResultCallback {
public static String FEATURE_CODE = "androidtv";
public static final int LIVE_TV_GUIDE_OPTION_ID = 1000;
public static final int LIVE_TV_RECORDINGS_OPTION_ID = 2000;
public static final int VIDEO_QUEUE_OPTION_ID = 3000;
private static final int SEARCH_PERMISSION = 0;
private ILogger logger;
private IConnectionManager connectionManager;
private PlaybackManager playbackManager;
private GsonJsonSerializer serializer;
private static TvApp app;
private UserDto currentUser;
private SystemInfo currentSystemInfo;
private BaseItemDto currentPlayingItem;
private BaseItemDto lastPlayedItem;
private PlaybackController playbackController;
private ApiClient loginApiClient;
private AudioManager audioManager;
private VolleyHttpClient httpClient;
private int autoBitrate;
private String directItemId;
private Typeface roboto;
private HashMap<String, DisplayPreferences> displayPrefsCache = new HashMap<>();
private String lastDeletedItemId = "";
private boolean isPaid = false;
private RegistrationInfo registrationInfo;
private Calendar lastPlayback = Calendar.getInstance();
private Calendar lastMoviePlayback = Calendar.getInstance();
private Calendar lastTvPlayback = Calendar.getInstance();
private Calendar lastLibraryChange = Calendar.getInstance();
private long lastVideoQueueChange = System.currentTimeMillis();
private long lastFavoriteUpdate = System.currentTimeMillis();
private long lastMusicPlayback = System.currentTimeMillis();
private long lastUserInteraction = System.currentTimeMillis();
private boolean searchAllowed = Build.VERSION.SDK_INT < 23;
private boolean audioMuted;
private BaseActivity currentActivity;
private LogonCredentials configuredAutoCredentials;
@Override
public void onCreate() {
super.onCreate();
logger = new ConsoleLogger();
app = (TvApp)getApplicationContext();
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
roboto = Typeface.createFromAsset(getAssets(), "fonts/Roboto-Light.ttf");
logger.Info("Application object created");
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!getApiClient().getServerInfo().getName().equals("Dev Server")) {
ex.printStackTrace();
new LogReporter().sendReport("Exception", new EmptyResponse() {
@Override
public void onResponse() {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
});
} else {
Log.e("MediaBrowserTv", "Uncaught exception is: ", ex);
ex.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
});
}
public static TvApp getApplication() {
return app;
}
public ILogger getLogger() {
return logger;
}
public void setLogger(ILogger value) {
logger = value;
}
public Typeface getDefaultFont() { return roboto; }
public IConnectionManager getConnectionManager() {
return connectionManager;
}
public void setConnectionManager(IConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
public UserDto getCurrentUser() {
return currentUser;
}
public void setCurrentUser(UserDto currentUser) {
this.currentUser = currentUser;
}
public GsonJsonSerializer getSerializer() {
return serializer;
}
public void setSerializer(GsonJsonSerializer serializer) {
this.serializer = serializer;
}
public ApiClient getApiClient() {
return connectionManager.GetApiClient(currentUser);
}
public BaseItemDto getCurrentPlayingItem() {
return currentPlayingItem;
}
public BaseActivity getCurrentActivity() {
return currentActivity;
}
public void setCurrentActivity(BaseActivity activity) {
currentActivity = activity;
}
public void setCurrentPlayingItem(BaseItemDto currentPlayingItem) {
this.currentPlayingItem = currentPlayingItem;
}
public ApiClient getLoginApiClient() {
return loginApiClient;
}
public void setLoginApiClient(ApiClient loginApiClient) {
this.loginApiClient = loginApiClient;
}
public void setAudioMuted(boolean value) {
audioMuted = value;
audioManager.setStreamMute(AudioManager.STREAM_MUSIC, audioMuted);
}
public boolean isAudioMuted() { return audioMuted; }
public PlaybackController getPlaybackController() {
return playbackController;
}
public void setPlaybackController(PlaybackController playbackController) {
this.playbackController = playbackController;
}
public SystemInfo getCurrentSystemInfo() { return currentSystemInfo; }
public void loadSystemInfo() {
if (getApiClient() != null) {
getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo response) {
currentSystemInfo = response;
logger.Info("Current server is " + response.getServerName() + " (ver " + response.getVersion() + ") running on " + response.getOperatingSystemDisplayName());
//Server compat warning
if (getCurrentActivity() != null && !Utils.versionGreaterThanOrEqual(currentSystemInfo.getVersion(), "3.0.5882.0")) {
new AlertDialog.Builder(getCurrentActivity())
.setTitle("Incompatible Server Version")
.setMessage("Please update your Emby Server to avoid potential seeking problems during playback.")
.setPositiveButton(getString(R.string.btn_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setCancelable(false)
.show();
}
}
@Override
public void onError(Exception exception) {
logger.ErrorException("Unable to obtain system info.", exception);
}
});
}
}
public void showSearch(final Activity activity, boolean musicOnly) {
if (!searchAllowed && ContextCompat.checkSelfPermission(activity,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
//request necessary permission
logger.Info("Requesting search permission...");
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO)) {
//show explanation
logger.Info("Show rationale for permission");
new AlertDialog.Builder(activity)
.setTitle("Search Permission")
.setMessage("Search requires permission to record audio in order to use the microphone for voice search")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
ActivityCompat.requestPermissions(activity, new String[] {Manifest.permission.RECORD_AUDIO}, SEARCH_PERMISSION);
}
}).show();
} else {
ActivityCompat.requestPermissions(activity, new String[] {Manifest.permission.RECORD_AUDIO}, SEARCH_PERMISSION);
}
} else {
showSearchInternal(activity, musicOnly);
}
}
private void showSearchInternal(Context activity, boolean musicOnly) {
Intent intent = new Intent(activity, SearchActivity.class);
if (musicOnly) intent.putExtra("MusicOnly", true);
activity.startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case SEARCH_PERMISSION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay!
searchAllowed = true;
showSearchInternal(getCurrentActivity(), false);
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Utils.showToast(this, "Search not allowed");
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
public void showMessage(String title, String msg) {
if (currentActivity != null) {
currentActivity.showMessage(title, msg);
}
}
public void showMessage(String title, String msg, int timeout, int iconResource) {
if (currentActivity != null) {
currentActivity.showMessage(title, msg, timeout, iconResource, null);
}
}
private long getLastNagTime() { return getSystemPrefs().getLong("lastNagTime",0); }
private void setLastNagTime(long time) { getSystemPrefs().edit().putLong("lastNagTime", System.currentTimeMillis()).commit(); }
public void premiereNag() {
if (!isRegistered() && System.currentTimeMillis() - (86400000 * 7) > getLastNagTime()) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (currentActivity != null && !currentActivity.isFinishing()) {
currentActivity.showMessage(getString(R.string.msg_premiere_nag_title), getString(R.string.msg_premiere_nag_msg), 10000);
setLastNagTime(System.currentTimeMillis());
}
}
},2500);
}
}
public LogonCredentials getConfiguredAutoCredentials() {
return configuredAutoCredentials;
}
public void setConfiguredAutoCredentials(LogonCredentials configuredAutoCredentials) {
this.configuredAutoCredentials = configuredAutoCredentials;
}
public SharedPreferences getPrefs() {
return PreferenceManager.getDefaultSharedPreferences(this);
}
public SharedPreferences getSystemPrefs() {
return getSharedPreferences("systemprefs", MODE_PRIVATE);
}
public String getConfigVersion() {
return getSystemPrefs().getString("sys_pref_config_version", "2");
}
public boolean getIsAutoLoginConfigured() {
return getPrefs().getString("pref_login_behavior", "0").equals("1") && getConfiguredAutoCredentials().getServerInfo().getId() != null;
}
public Calendar getLastMoviePlayback() {
return lastMoviePlayback.after(lastPlayback) ? lastMoviePlayback : lastPlayback;
}
public void setLastMoviePlayback(Calendar lastMoviePlayback) {
this.lastMoviePlayback = lastMoviePlayback;
this.lastPlayback = lastMoviePlayback;
}
public void setLastFavoriteUpdate(long time) { lastFavoriteUpdate = time; }
public long getLastFavoriteUpdate() { return lastFavoriteUpdate; }
public void setLastMusicPlayback(long time) { lastMusicPlayback = time; }
public long getLastMusicPlayback() { return lastMusicPlayback; }
public boolean directStreamLiveTv() { return getPrefs().getBoolean("pref_live_direct", true); }
public void setDirectStreamLiveTv(boolean value) { getPrefs().edit().putBoolean("pref_live_direct", value).commit(); }
public Calendar getLastTvPlayback() {
return lastTvPlayback.after(lastPlayback) ? lastTvPlayback : lastPlayback;
}
public void setLastTvPlayback(Calendar lastTvPlayback) {
this.lastTvPlayback = lastTvPlayback;
this.lastPlayback = lastTvPlayback;
}
public Calendar getLastLibraryChange() {
return lastLibraryChange;
}
public void setLastLibraryChange(Calendar lastLibraryChange) {
this.lastLibraryChange = lastLibraryChange;
}
public Calendar getLastPlayback() {
return lastPlayback;
}
public void setLastPlayback(Calendar lastPlayback) {
this.lastPlayback = lastPlayback;
}
public long getLastUserInteraction() {
return lastUserInteraction;
}
public void setLastUserInteraction(long lastUserInteraction) {
this.lastUserInteraction = lastUserInteraction;
}
public boolean checkPaidCache() {
isPaid = getSystemPrefs().getString("kv","").equals(getApiClient().getDeviceId());
logger.Info("Paid cache check: " + isPaid);
return isPaid;
}
public boolean isPaid() {
return isPaid;
}
public void setPaid(boolean isPaid) {
this.isPaid = isPaid;
getSystemPrefs().edit().putString("kv", isPaid ? getApiClient().getDeviceId() : "").commit();
}
public RegistrationInfo getRegistrationInfo() {
return registrationInfo;
}
public void setRegistrationInfo(RegistrationInfo registrationInfo) {
this.registrationInfo = registrationInfo;
}
public boolean isValid() {
return isPaid || (registrationInfo != null && (registrationInfo.getIsRegistered() || registrationInfo.getIsTrial()));
}
public boolean isRegistered() {
return registrationInfo != null && registrationInfo.getIsRegistered();
}
public boolean isTrial() {
return registrationInfo != null && registrationInfo.getIsTrial() && !isPaid;
}
public void validate() {
new AppValidator().validate();
}
public String getRegistrationString() {
return isTrial() ? "In Trial. Expires " + DateUtils.getRelativeTimeSpanString(Utils.convertToLocalDate(registrationInfo.getExpirationDate()).getTime()).toString() :
isValid() ? "Registered" : "Expired";
}
public PlaybackManager getPlaybackManager() {
return playbackManager;
}
public void setPlaybackManager(PlaybackManager playbackManager) {
this.playbackManager = playbackManager;
}
public Drawable getDrawableCompat(int id) {
// if (Build.VERSION.SDK_INT >= 21) {
// return getDrawable(id);
// }
return getResources().getDrawable(id);
}
public boolean isConnectLogin() {
return getSystemPrefs().getBoolean("sys_pref_connect_login", false);
}
public void setConnectLogin(boolean value) {
TvApp.getApplication().getSystemPrefs().edit().putBoolean("sys_pref_connect_login", value).commit();
}
public boolean isPlayingVideo() {
return playbackController != null && currentActivity != null && currentActivity instanceof PlaybackOverlayActivity;
}
public void stopPlayback() {
if (isPlayingVideo()) {
currentActivity.finish();
} else if (MediaManager.isPlayingAudio()) {
MediaManager.stopAudio();
}
}
public void pausePlayback() {
if (MediaManager.isPlayingAudio()) {
MediaManager.pauseAudio();
} else if (isPlayingVideo()) {
playbackController.playPause();
}
}
public void unPausePlayback() {
if (isPlayingVideo()) {
playbackController.playPause();
} else if (MediaManager.hasAudioQueueItems()) {
MediaManager.resumeAudio();
}
}
public void playbackNext() {
if (isPlayingVideo()) {
playbackController.next();
} else if (MediaManager.hasAudioQueueItems()) {
MediaManager.nextAudioItem();
}
}
public void playbackPrev() {
if (isPlayingVideo()) {
playbackController.prev();
} else if (MediaManager.hasAudioQueueItems()) {
MediaManager.prevAudioItem();
}
}
public void playbackSeek(int pos) {
if (isPlayingVideo()) {
playbackController.seek(pos);
}
}
public void playbackJump() {
if (isPlayingVideo()) {
playbackController.skip(30000);
}
}
public void playbackJumpBack() {
if (playbackController != null) {
playbackController.skip(-11000);
}
}
public DisplayPreferences getCachedDisplayPrefs(String key) {
return displayPrefsCache.containsKey(key) ? displayPrefsCache.get(key) : new DisplayPreferences();
}
public void updateDisplayPrefs(DisplayPreferences preferences) {
displayPrefsCache.put(preferences.getId(), preferences);
getApiClient().UpdateDisplayPreferencesAsync(preferences, getCurrentUser().getId(), "ATV", new EmptyResponse());
logger.Debug("Display prefs updated for "+preferences.getId()+" isFavorite: "+preferences.getCustomPrefs().get("FavoriteOnly"));
}
public void getDisplayPrefsAsync(final String key, final Response<DisplayPreferences> outerResponse) {
if (displayPrefsCache.containsKey(key)) {
logger.Debug("Display prefs loaded from cache "+key);
outerResponse.onResponse(displayPrefsCache.get(key));
} else {
getApiClient().GetDisplayPreferencesAsync(key, getCurrentUser().getId(), "ATV", new Response<DisplayPreferences>(){
@Override
public void onResponse(DisplayPreferences response) {
if (response.getSortBy() == null) response.setSortBy("SortName");
if (response.getCustomPrefs() == null) response.setCustomPrefs(new HashMap<String, String>());
displayPrefsCache.put(key, response);
logger.Debug("Display prefs loaded and saved in cache " + key);
outerResponse.onResponse(response);
}
@Override
public void onError(Exception exception) {
//Continue with defaults
logger.ErrorException("Unable to load display prefs ", exception);
DisplayPreferences prefs = new DisplayPreferences();
prefs.setId(key);
prefs.setSortBy("SortName");
prefs.setCustomPrefs(new HashMap<String, String>());
outerResponse.onResponse(prefs);
}
});
}
}
public int getAutoBitrate() {
return autoBitrate;
}
public void determineAutoBitrate() {
if (getApiClient() == null) return;
getApiClient().detectBitrate(new Response<Long>() {
@Override
public void onResponse(Long response) {
autoBitrate = response.intValue();
logger.Info("Auto bitrate set to: "+autoBitrate);
}
});
}
public String getDirectItemId() {
return directItemId;
}
public void setDirectItemId(String directItemId) {
this.directItemId = directItemId;
}
public String getLastDeletedItemId() {
return lastDeletedItemId;
}
public void setLastDeletedItemId(String lastDeletedItemId) {
this.lastDeletedItemId = lastDeletedItemId;
}
public VolleyHttpClient getHttpClient() {
return httpClient;
}
public void setHttpClient(VolleyHttpClient httpClient) {
this.httpClient = httpClient;
}
public boolean isSearchAllowed() {
return searchAllowed;
}
public void setSearchAllowed(boolean searchAllowed) {
this.searchAllowed = searchAllowed;
}
public BaseItemDto getLastPlayedItem() {
return lastPlayedItem;
}
public void setLastPlayedItem(BaseItemDto lastPlayedItem) {
this.lastPlayedItem = lastPlayedItem;
}
public long getLastVideoQueueChange() {
return lastVideoQueueChange;
}
public void setLastVideoQueueChange(long lastVideoQueueChange) {
this.lastVideoQueueChange = lastVideoQueueChange;
}
}