Java tutorial
/* * Copyright 2015 Synced Synapse. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xbmc.kore.host; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Base64; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.picasso.OkHttpDownloader; import com.squareup.picasso.Picasso; import org.xbmc.kore.BuildConfig; import org.xbmc.kore.Settings; import org.xbmc.kore.jsonrpc.HostConnection; import org.xbmc.kore.provider.MediaContract; import org.xbmc.kore.utils.BasicAuthUrlConnectionDownloader; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.NetUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; /** * Manages XBMC Hosts * Singleton that loads the list of registered hosts, keeps a * {@link HostConnection} to the active host * and allows for creation and removal of hosts */ public class HostManager { private static final String TAG = LogUtils.makeLogTag(HostManager.class); // Singleton instance private static volatile HostManager instance = null; private Context context; /** * Arraylist that will hold all the hosts in the database */ private ArrayList<HostInfo> hosts = new ArrayList<HostInfo>(); /** * Current host */ private HostInfo currentHostInfo = null; /** * Current host connection */ private HostConnection currentHostConnection = null; /** * Picasso to download images from current XBMC */ private Picasso currentPicasso = null; /** * Current connection observer */ private HostConnectionObserver currentHostConnectionObserver = null; /** * Singleton constructor * @param context Context (can pass Activity context, will get App Context) */ protected HostManager(Context context) { this.context = context.getApplicationContext(); } /** * Singleton access method * @param context Android app context * @return HostManager singleton */ public static HostManager getInstance(Context context) { if (instance == null) { synchronized (HostManager.class) { if (instance == null) { instance = new HostManager(context); } } } return instance; } /** * Returns the current host list * @return Host list */ public ArrayList<HostInfo> getHosts() { return getHosts(false); } /** * Returns the current host list, maybe forcing a reload from the database * @param forcedReload Whether to force a reload from the database * @return Host list */ public ArrayList<HostInfo> getHosts(boolean forcedReload) { if (forcedReload || (hosts.size() == 0)) { hosts.clear(); Cursor cursor = context.getContentResolver().query(MediaContract.Hosts.CONTENT_URI, MediaContract.Hosts.ALL_COLUMNS, null, null, null); if (cursor == null) return hosts; if (cursor.getCount() > 0) { while (cursor.moveToNext()) { int idx = 0; int id = cursor.getInt(idx++); long updated = cursor.getLong(idx++); String name = cursor.getString(idx++); String address = cursor.getString(idx++); int protocol = cursor.getInt(idx++); int httpPort = cursor.getInt(idx++); int tcpPort = cursor.getInt(idx++); String username = cursor.getString(idx++); String password = cursor.getString(idx++); String macAddress = cursor.getString(idx++); int wolPort = cursor.getInt(idx++); boolean useEventServer = (cursor.getInt(idx++) != 0); int eventServerPort = cursor.getInt(idx++); hosts.add(new HostInfo(id, name, address, protocol, httpPort, tcpPort, username, password, macAddress, wolPort, useEventServer, eventServerPort)); } } cursor.close(); } return hosts; } /** * Returns the current active host info * @return Active host info */ public HostInfo getHostInfo() { if (currentHostInfo == null) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); int currentHostId = prefs.getInt(Settings.KEY_PREF_CURRENT_HOST_ID, Settings.DEFAULT_PREF_CURRENT_HOST_ID); ArrayList<HostInfo> hosts = getHosts(); // No host selected. Check if there are hosts configured and default to the first one if (currentHostId == -1) { if (hosts.size() > 0) { currentHostInfo = hosts.get(0); currentHostId = currentHostInfo.getId(); prefs.edit().putInt(Settings.KEY_PREF_CURRENT_HOST_ID, currentHostId).apply(); } } else { for (HostInfo host : hosts) { if (host.getId() == currentHostId) { currentHostInfo = host; break; } } } } return currentHostInfo; } /** * Returns the current active host connection * @return Active host connection */ public HostConnection getConnection() { if (currentHostConnection == null) { currentHostInfo = getHostInfo(); if (currentHostInfo != null) { currentHostConnection = new HostConnection(currentHostInfo); } } return currentHostConnection; } /** * Returns the current host {@link Picasso} image downloader * @return {@link Picasso} instance suitable to download images from the current xbmc */ public Picasso getPicasso() { if (currentPicasso == null) { currentHostInfo = getHostInfo(); if (currentHostInfo != null) { // currentPicasso = new Picasso.Builder(context) // .downloader(new BasicAuthUrlConnectionDownloader(context, // currentHostInfo.getUsername(), currentHostInfo.getPassword())) // .indicatorsEnabled(BuildConfig.DEBUG) // .build(); // Http client should already handle authentication OkHttpClient picassoClient = getConnection().getOkHttpClient().clone(); // OkHttpClient picassoClient = new OkHttpClient(); // // Set authentication on the client // if (!TextUtils.isEmpty(currentHostInfo.getUsername())) { // picassoClient.interceptors().add(new Interceptor() { // @Override // public Response intercept(Chain chain) throws IOException { // // String creds = currentHostInfo.getUsername() + ":" + currentHostInfo.getPassword(); // Request newRequest = chain.request().newBuilder() // .addHeader("Authorization", // "Basic " + Base64.encodeToString(creds.getBytes(), Base64.NO_WRAP)) // .build(); // return chain.proceed(newRequest); // } // }); // } // Set cache File cacheDir = NetUtils.createDefaultCacheDir(context); long cacheSize = NetUtils.calculateDiskCacheSize(cacheDir); picassoClient.setCache(new com.squareup.okhttp.Cache(cacheDir, cacheSize)); currentPicasso = new Picasso.Builder(context).downloader(new OkHttpDownloader(picassoClient)) // .indicatorsEnabled(BuildConfig.DEBUG) .build(); } } return currentPicasso; } /** * Returns the current {@link HostConnectionObserver} for the current connection * @return The {@link HostConnectionObserver} for the current connection */ public HostConnectionObserver getHostConnectionObserver() { if (currentHostConnectionObserver == null) { currentHostConnection = getConnection(); if (currentHostConnection != null) { currentHostConnectionObserver = new HostConnectionObserver(currentHostConnection); } } return currentHostConnectionObserver; } /** * Sets the current host. * @param hostInfo Host info */ public void switchHost(HostInfo hostInfo) { releaseCurrentHost(); currentHostInfo = hostInfo; if (currentHostInfo != null) { PreferenceManager.getDefaultSharedPreferences(context).edit() .putInt(Settings.KEY_PREF_CURRENT_HOST_ID, currentHostInfo.getId()).apply(); } } // /** // * Sets the current host. // * Throws {@link java.lang.IllegalArgumentException} if the host doesn't exist // * @param hostId Host id // */ // public void switchHost(int hostId) { // ArrayList<HostInfo> hosts = getHosts(); // HostInfo newHostInfo = null; // // for (HostInfo host : hosts) { // if (host.getId() == hostId) { // newHostInfo = host; // break; // } // } // // if (newHostInfo == null) { // throw new IllegalArgumentException("Host doesn't exist!"); // } // switchHost(newHostInfo); // } /** * Adds a new XBMC host to the database * @param hostInfo Host to add * @return Newly created {@link org.xbmc.kore.host.HostInfo} */ public HostInfo addHost(HostInfo hostInfo) { return addHost(hostInfo.getName(), hostInfo.getAddress(), hostInfo.getProtocol(), hostInfo.getHttpPort(), hostInfo.getTcpPort(), hostInfo.getUsername(), hostInfo.getPassword(), hostInfo.getMacAddress(), hostInfo.getWolPort(), hostInfo.getUseEventServer(), hostInfo.getEventServerPort()); } /** * Adds a new XBMC host to the database * @param name Name of this instance * @param address Hostname or IP Address * @param protocol Protocol to use * @param httpPort HTTP port * @param tcpPort TCP port * @param username Username for HTTP * @param password Password for HTTP * @return Newly created {@link org.xbmc.kore.host.HostInfo} */ public HostInfo addHost(String name, String address, int protocol, int httpPort, int tcpPort, String username, String password, String macAddress, int wolPort, boolean useEventServer, int eventServerPort) { ContentValues values = new ContentValues(); values.put(MediaContract.HostsColumns.NAME, name); values.put(MediaContract.HostsColumns.ADDRESS, address); values.put(MediaContract.HostsColumns.PROTOCOL, protocol); values.put(MediaContract.HostsColumns.HTTP_PORT, httpPort); values.put(MediaContract.HostsColumns.TCP_PORT, tcpPort); values.put(MediaContract.HostsColumns.USERNAME, username); values.put(MediaContract.HostsColumns.PASSWORD, password); values.put(MediaContract.HostsColumns.MAC_ADDRESS, macAddress); values.put(MediaContract.HostsColumns.WOL_PORT, wolPort); values.put(MediaContract.HostsColumns.USE_EVENT_SERVER, useEventServer); values.put(MediaContract.HostsColumns.EVENT_SERVER_PORT, eventServerPort); Uri newUri = context.getContentResolver().insert(MediaContract.Hosts.CONTENT_URI, values); long newId = Long.valueOf(MediaContract.Hosts.getHostId(newUri)); // Refresh the list and return the created host hosts = getHosts(true); HostInfo newHost = null; for (HostInfo host : hosts) { if (host.getId() == newId) { newHost = host; break; } } return newHost; } /** * Edits a host on the database * @param hostId Id of the host to edit * @param newHostInfo New values to update * @return New {@link HostInfo} object */ public HostInfo editHost(int hostId, HostInfo newHostInfo) { ContentValues values = new ContentValues(); values.put(MediaContract.HostsColumns.NAME, newHostInfo.getName()); values.put(MediaContract.HostsColumns.ADDRESS, newHostInfo.getAddress()); values.put(MediaContract.HostsColumns.PROTOCOL, newHostInfo.getProtocol()); values.put(MediaContract.HostsColumns.HTTP_PORT, newHostInfo.getHttpPort()); values.put(MediaContract.HostsColumns.TCP_PORT, newHostInfo.getTcpPort()); values.put(MediaContract.HostsColumns.USERNAME, newHostInfo.getUsername()); values.put(MediaContract.HostsColumns.PASSWORD, newHostInfo.getPassword()); values.put(MediaContract.HostsColumns.MAC_ADDRESS, newHostInfo.getMacAddress()); values.put(MediaContract.HostsColumns.WOL_PORT, newHostInfo.getWolPort()); values.put(MediaContract.HostsColumns.USE_EVENT_SERVER, newHostInfo.getUseEventServer()); values.put(MediaContract.HostsColumns.EVENT_SERVER_PORT, newHostInfo.getEventServerPort()); context.getContentResolver().update(MediaContract.Hosts.buildHostUri(hostId), values, null, null); // Refresh the list and return the created host hosts = getHosts(true); HostInfo newHost = null; for (HostInfo host : hosts) { if (host.getId() == hostId) { newHost = host; break; } } return newHost; } /** * Deletes a host from the database. * If the delete host is the current one, we will try too change the current one to another * or set it to null if there's no other * @param hostId Id of the host to delete */ public void deleteHost(final int hostId) { // Async call delete. The triggers to delete all host information can take some time new Thread(new Runnable() { @Override public void run() { context.getContentResolver().delete(MediaContract.Hosts.buildHostUri(hostId), null, null); } }).start(); // Refresh information int index = -1; for (int i = 0; i < hosts.size(); i++) { if (hosts.get(i).getId() == hostId) { index = i; break; } } if (index != -1) hosts.remove(index); // If we just deleted the current connection, switch to another if ((currentHostInfo != null) && (currentHostInfo.getId() == hostId)) { releaseCurrentHost(); if (hosts.size() > 0) switchHost(hosts.get(0)); } } /** * Releases all state related to the current connection */ private void releaseCurrentHost() { if (currentHostConnectionObserver != null) { currentHostConnectionObserver.stopObserving(); currentHostConnectionObserver = null; } if (currentHostConnection != null) { currentHostConnection.disconnect(); currentHostConnection = null; } if (currentPicasso != null) { // Calling shutdown here causes a picasso error: // Handler (com.squareup.picasso.Stats$StatsHandler) {41b13d40} sending message to a Handler on a dead thread // Check: https://github.com/square/picasso/issues/445 // So, for now, just let it be... // currentPicasso.shutdown(); currentPicasso = null; } } }