package com.wigwamlabs.spotify; import android.annotation.TargetApi; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; import android.os.Handler; import android.provider.Settings; import proguard.annotation.Keep; import java.util.ArrayList; import java.util.ConcurrentModificationException; public class Session extends NativeItem { public static final int CONNECTION_STATE_LOGGED_OUT = 0; public static final int CONNECTION_STATE_LOGGED_IN = 1; public static final int CONNECTION_STATE_DISCONNECTED = 2; public static final int CONNECTION_STATE_UNDEFINED = 3; public static final int CONNECTION_STATE_OFFLINE = 4; public static final int BITRATE_96K = 2; public static final int BITRATE_160K = 0; public static final int BITRATE_320K = 1; private static final int CONNECTION_RULES_DOWNLOAD_OVER_MOBILE = 0x1 | 0x4 | 0x8; // network | mobile | wifi private static final int CONNECTION_RULES_DEFAULT = 0x1 | 0x8; // network | wifi private static final Handler mHandler = new Handler(); private final Context mContext; private final Preferences mPreferences; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ConnectivityManager mConnectivityManager; private int mState; private Player mPlayer; private ImageProvider mImageProvider; private int mSyncApproxTotalTracks = 0; private int mSyncLastTrackDownloaded; private boolean mLastSyncStatus; public Session(Context context, String settingsPath, String cachePath, String deviceId) { super(0); mContext = context; if (settingsPath == null) { settingsPath = context.getFilesDir().getAbsolutePath(); } if (cachePath == null) { cachePath = context.getCacheDir().getAbsolutePath(); } if (deviceId == null) { deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); } setHandle(nativeCreate(settingsPath, cachePath, deviceId)); mPreferences = new Preferences(context); mPreferences.setDefaultValues(); mPreferences.setCallback(new Preferences.Callback() { @Override public void onStreamingBitratePreferenceChanged(int bitrate) { nativeSetStreamingBitrate(bitrate); } @Override public void onOfflineBitratePreferenceChanged(int bitrate) { nativeSetOfflineBitrate(bitrate); } @Override public void onConnectionRulesPreferenceChanged(boolean downloadOverMobile) { nativeSetConnectionRules(downloadOverMobile ? CONNECTION_RULES_DOWNLOAD_OVER_MOBILE : CONNECTION_RULES_DEFAULT); } }); nativeSetStreamingBitrate(mPreferences.getStreamingBitrate()); nativeSetOfflineBitrate(mPreferences.getOfflineBitrate()); nativeSetConnectionRules(mPreferences.getDownloadOverMobile() ? CONNECTION_RULES_DOWNLOAD_OVER_MOBILE : CONNECTION_RULES_DEFAULT); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); updateConnectionType(); } private static native void nativeInitClass(); @Override public void destroy() { super.destroy(); if (mPreferences != null) { mPreferences.setCallback(null); } if (mPlayer != null) { mPlayer.destroy(); mPlayer = null; } if (mImageProvider != null) { mImageProvider.destroy(); mImageProvider = null; } } public boolean relogin() { // libspotify's relogin support is not reliable so we store the credentials ourselves final String username = mPreferences.getUsername(); final String blob = mPreferences.getCredentialsBlob(); if (username != null && blob != null) { nativeLogin(username, null, blob); return true; } return false; } public void login(String username, String password) { nativeLogin(username, password, null); mPreferences.setUsername(username); } public void logout() { nativeLogout(); } public PlaylistContainer getPlaylistContainer() { final int handle = nativeGetPlaylistContainer(); if (handle == 0) { return null; } return new PlaylistContainer(handle); } private native int nativeCreate(String settingsPath, String cachePath, String deviceId); @Override native void nativeDestroy(); private native void nativeSetStreamingBitrate(int bitrate); private native void nativeSetOfflineBitrate(int bitrate); private native void nativeSetConnectionType(int type); private native void nativeSetConnectionRules(int connectionRules); private native int nativeGetConnectionState(); public void addCallback(Callback callback, boolean callbackNow) { if (mCallbacks.contains(callback)) { return; } mCallbacks.add(callback); if (callbackNow) { callback.onConnectionStateUpdated(nativeGetConnectionState()); callback.onOfflineTracksToSyncChanged(mLastSyncStatus, mSyncApproxTotalTracks - mSyncLastTrackDownloaded, mSyncApproxTotalTracks); } } public void removeCallback(Callback callback) { mCallbacks.remove(callback); } private native void nativeLogin(String username, String password, String blob); private native void nativeLogout(); private native int nativeGetPlaylistContainer(); private native int nativeGetPlayer(); public Player getPlayer() { if (mPlayer == null) { final int handle = nativeGetPlayer(); mPlayer = new Player(mContext, handle, getImageProvider()); } return mPlayer; } public ImageProvider getImageProvider() { if (mImageProvider == null) { mImageProvider = new ImageProvider(this); } return mImageProvider; } @Keep void onLoggedIn(final int error) { mHandler.post(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { try { for (Callback callback : mCallbacks) { callback.onLoggedIn(error); } return; } catch (ConcurrentModificationException e) { // retry } } } }); } @Keep void onMetadataUpdated() { Debug.logTemp("onMetadataUpdated()"); } @Keep void onCredentialsBlobUpdated(final String blob) { mHandler.post(new Runnable() { @Override public void run() { mPreferences.setCredentialsBlob(blob); } }); } public void updateConnectionType() { final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); int type = 0; // unknown if (isActiveNetworkMetered()) { type = 2; // treat it as mobile } else if (networkInfo != null) { if (networkInfo.isRoaming()) { type = 3; // mobile roaming } else { switch (networkInfo.getType()) { case ConnectivityManager.TYPE_WIFI: type = 4; // wifi break; case ConnectivityManager.TYPE_ETHERNET: type = 5; // wired break; default: case ConnectivityManager.TYPE_MOBILE: type = 2; // mobile break; } } } nativeSetConnectionType(type); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private boolean isActiveNetworkMetered() { return Build.VERSION.SDK_INT >= 16 && mConnectivityManager.isActiveNetworkMetered(); } public int getConnectionState() { return nativeGetConnectionState(); } @Keep void onConnectionStateUpdated(int state) { mState = state; mHandler.post(new Runnable() { @Override public void run() { for (Callback callback : mCallbacks) { callback.onConnectionStateUpdated(mState); } } }); } @Keep private void onOfflineTracksToSyncChanged(final boolean syncing, final int remainingTracks) { mHandler.post(new Runnable() { @Override public void run() { mLastSyncStatus = syncing; // deal with tracks added to queue if (remainingTracks > mSyncApproxTotalTracks) { mSyncApproxTotalTracks = remainingTracks + mSyncLastTrackDownloaded; } // if (remainingTracks == 0) { mSyncApproxTotalTracks = 0; mSyncLastTrackDownloaded = 0; } else { mSyncLastTrackDownloaded = mSyncApproxTotalTracks - remainingTracks; } for (Callback callback : mCallbacks) { callback.onOfflineTracksToSyncChanged(syncing, remainingTracks, mSyncApproxTotalTracks); } } }); } static { System.loadLibrary("spotify"); System.loadLibrary("spotify-jni"); nativeInitClass(); } public interface Callback { void onLoggedIn(int error); void onConnectionStateUpdated(int state); void onOfflineTracksToSyncChanged(boolean syncing, int remainingTracks, int approxTotalTracks); } }