2016-12-18 18:41:30 +01:00
|
|
|
/*
|
|
|
|
This file is part of Subsonic.
|
|
|
|
|
|
|
|
Subsonic is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Subsonic is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Copyright 2009 (C) Sindre Mehus
|
|
|
|
*/
|
2017-01-09 08:01:12 +01:00
|
|
|
package net.nullsum.audinaut.util;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
import android.app.Activity;
|
|
|
|
import android.content.ClipData;
|
2018-03-25 03:28:28 +02:00
|
|
|
import android.content.ClipboardManager;
|
2016-12-18 18:41:30 +01:00
|
|
|
import android.content.ComponentName;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.DialogInterface;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.BitmapFactory;
|
|
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.media.AudioManager;
|
|
|
|
import android.media.AudioManager.OnAudioFocusChangeListener;
|
|
|
|
import android.net.ConnectivityManager;
|
|
|
|
import android.net.NetworkInfo;
|
|
|
|
import android.net.wifi.WifiManager;
|
|
|
|
import android.os.Build;
|
|
|
|
import android.os.Environment;
|
2019-08-24 20:24:07 +02:00
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.annotation.StringRes;
|
|
|
|
import androidx.appcompat.app.AlertDialog;
|
2016-12-18 18:41:30 +01:00
|
|
|
import android.text.SpannableString;
|
|
|
|
import android.text.method.LinkMovementMethod;
|
|
|
|
import android.text.util.Linkify;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.util.SparseArray;
|
|
|
|
import android.view.Gravity;
|
|
|
|
import android.widget.ListView;
|
|
|
|
import android.widget.TextView;
|
|
|
|
import android.widget.Toast;
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2017-01-09 08:01:12 +01:00
|
|
|
import net.nullsum.audinaut.R;
|
|
|
|
import net.nullsum.audinaut.adapter.DetailsAdapter;
|
|
|
|
import net.nullsum.audinaut.domain.MusicDirectory;
|
|
|
|
import net.nullsum.audinaut.domain.PlayerState;
|
|
|
|
import net.nullsum.audinaut.domain.RepeatMode;
|
|
|
|
import net.nullsum.audinaut.receiver.MediaButtonIntentReceiver;
|
|
|
|
import net.nullsum.audinaut.service.DownloadService;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.Closeable;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.math.BigInteger;
|
2019-01-22 06:08:49 +01:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2016-12-18 18:41:30 +01:00
|
|
|
import java.security.MessageDigest;
|
|
|
|
import java.security.SecureRandom;
|
|
|
|
import java.text.DecimalFormat;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
2019-03-01 00:04:52 +01:00
|
|
|
import java.util.Map;
|
2016-12-18 18:41:30 +01:00
|
|
|
import java.util.Random;
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
import okhttp3.HttpUrl;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Sindre Mehus
|
|
|
|
* @version $Id$
|
|
|
|
*/
|
|
|
|
public final class Util {
|
2018-03-25 03:28:28 +02:00
|
|
|
private static final String EVENT_META_CHANGED = "net.nullsum.audinaut.EVENT_META_CHANGED";
|
|
|
|
private static final String EVENT_PLAYSTATE_CHANGED = "net.nullsum.audinaut.EVENT_PLAYSTATE_CHANGED";
|
|
|
|
private static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
|
|
|
|
private static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged";
|
2016-12-18 18:41:30 +01:00
|
|
|
private static final String TAG = Util.class.getSimpleName();
|
|
|
|
private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB");
|
|
|
|
private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB");
|
|
|
|
private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB");
|
2018-03-25 03:28:28 +02:00
|
|
|
// Used by hexEncode()
|
|
|
|
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
|
|
|
// private static Map<Integer, Pair<String, String>> tokens = new HashMap<>();
|
|
|
|
private static final SparseArray<Pair<String, String>> tokens = new SparseArray<>();
|
2016-12-18 18:41:30 +01:00
|
|
|
private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null;
|
|
|
|
private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null;
|
|
|
|
private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null;
|
|
|
|
private static DecimalFormat BYTE_LOCALIZED_FORMAT = null;
|
2018-03-24 20:25:12 +01:00
|
|
|
private static OnAudioFocusChangeListener focusListener;
|
|
|
|
private static boolean pauseFocus = false;
|
|
|
|
private static boolean lowerFocus = false;
|
2016-12-18 18:41:30 +01:00
|
|
|
private static Toast toast;
|
2018-03-24 20:25:12 +01:00
|
|
|
private static Random random;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
private Util() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isOffline(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
2018-03-24 20:25:12 +01:00
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void setOffline(Context context, boolean offline) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, offline);
|
|
|
|
editor.apply();
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isScreenLitOnDownload(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static RepeatMode getRepeatMode(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name()));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void setRepeatMode(Context context, RepeatMode repeatMode) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name());
|
2017-06-11 17:09:09 +02:00
|
|
|
editor.apply();
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static void setActiveServer(Context context, int instance) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
|
2017-06-11 17:09:09 +02:00
|
|
|
editor.apply();
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static int getActiveServer(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
2018-03-24 20:25:12 +01:00
|
|
|
// Don't allow the SERVER_INSTANCE to ever be 0
|
2016-12-18 18:41:30 +01:00
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false) ? 0 : Math.max(1, prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1));
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static int getMostRecentActiveServer(Context context) {
|
2018-03-24 20:25:12 +01:00
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return Math.max(1, prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getServerCount(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void removeInstanceName(Context context, int instance, int activeInstance) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
|
|
|
|
int newInstance = instance + 1;
|
|
|
|
|
|
|
|
// Get what the +1 server details are
|
|
|
|
String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
|
|
|
|
String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
|
|
|
|
String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
|
|
|
|
String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
|
|
|
|
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
|
|
|
|
String musicFolderId = prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + newInstance, null);
|
|
|
|
|
|
|
|
// Store the +1 server details in the to be deleted instance
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
|
|
|
|
|
|
|
|
// Delete the +1 server instance
|
|
|
|
// Calling method will loop up to fill this in if +2 server exists
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + newInstance, null);
|
|
|
|
editor.apply();
|
|
|
|
|
|
|
|
if (instance == activeInstance) {
|
2018-03-25 03:28:28 +02:00
|
|
|
if (instance != 1) {
|
2018-03-24 20:25:12 +01:00
|
|
|
Util.setActiveServer(context, 1);
|
|
|
|
} else {
|
|
|
|
Util.setOffline(context, true);
|
|
|
|
}
|
|
|
|
} else if (newInstance == activeInstance) {
|
|
|
|
Util.setActiveServer(context, instance);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getServerName(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
2016-12-18 18:41:30 +01:00
|
|
|
return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
public static String getServerName(Context context, int instance) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void setSelectedMusicFolderId(Context context, String musicFolderId) {
|
|
|
|
int instance = getActiveServer(context);
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
|
2017-06-11 17:09:09 +02:00
|
|
|
editor.apply();
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static String getSelectedMusicFolderId(Context context) {
|
|
|
|
return getSelectedMusicFolderId(context, getActiveServer(context));
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static String getSelectedMusicFolderId(Context context, int instance) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean getAlbumListsPerFolder(Context context, int instance) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_ALBUMS_PER_FOLDER + instance, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean getDisplayTrack(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
2016-12-18 18:41:30 +01:00
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_DISPLAY_TRACK, false);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
public static int getMaxBitrate(Context context) {
|
|
|
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
|
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
|
|
|
if (networkInfo == null) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE, "0"));
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
public static int getPreloadCount(Context context) {
|
2018-03-24 20:25:12 +01:00
|
|
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
2016-12-18 18:41:30 +01:00
|
|
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
|
|
|
if (networkInfo == null) {
|
|
|
|
return 3;
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
SharedPreferences prefs = getPreferences(context);
|
2018-03-24 20:25:12 +01:00
|
|
|
boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
|
2016-12-18 18:41:30 +01:00
|
|
|
int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1"));
|
|
|
|
return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getCacheSizeMB(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1"));
|
|
|
|
return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static boolean isBatchMode(Context context) {
|
|
|
|
return Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, false);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static void setBatchMode(Context context, boolean batchMode) {
|
|
|
|
Util.getPreferences(context).edit().putBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, batchMode).apply();
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
public static String getRestUrl(Context context) {
|
2019-03-01 00:04:52 +01:00
|
|
|
return getRestUrl(context, null, true, null);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2019-03-01 00:04:52 +01:00
|
|
|
// used
|
|
|
|
public static String getRestUrl(Context context, String method, boolean allowAltAddress, @Nullable Map<String, String> parameters) {
|
2018-03-24 20:25:12 +01:00
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
2019-03-01 00:04:52 +01:00
|
|
|
return getRestUrl(context, method, prefs, instance, allowAltAddress, parameters);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2019-03-01 00:04:52 +01:00
|
|
|
public static String getRestUrl(Context context, String method, int instance, boolean allowAltAddress, @Nullable Map<String, String> parameters) {
|
2018-03-24 20:25:12 +01:00
|
|
|
SharedPreferences prefs = getPreferences(context);
|
2019-03-01 00:04:52 +01:00
|
|
|
return getRestUrl(context, method, prefs, instance, allowAltAddress, parameters);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
2019-03-01 00:04:52 +01:00
|
|
|
private static String getRestUrl(Context context, String method, SharedPreferences prefs, int instance, boolean allowAltAddress, @Nullable Map<String, String> parameters) {
|
2017-03-12 20:59:47 +01:00
|
|
|
String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
|
|
|
|
|
|
|
|
HttpUrl.Builder builder;
|
|
|
|
builder = HttpUrl.parse(serverUrl).newBuilder();
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (allowAltAddress && Util.isWifiConnected(context)) {
|
2017-03-12 20:59:47 +01:00
|
|
|
String SSID = prefs.getString(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance, "");
|
2018-03-25 03:28:28 +02:00
|
|
|
if (!SSID.isEmpty()) {
|
2017-03-12 20:59:47 +01:00
|
|
|
String currentSSID = Util.getSSID(context);
|
|
|
|
|
|
|
|
String[] ssidParts = SSID.split(",");
|
2018-04-25 02:20:20 +02:00
|
|
|
if (SSID.equals(currentSSID) || Arrays.asList(ssidParts).contains(currentSSID)) {
|
2017-03-12 20:59:47 +01:00
|
|
|
String internalUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance, null);
|
|
|
|
if (internalUrl != null && !"".equals(internalUrl) && !"http://".equals(internalUrl)) {
|
|
|
|
serverUrl = internalUrl;
|
|
|
|
builder = HttpUrl.parse(serverUrl).newBuilder();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2017-03-12 20:59:47 +01:00
|
|
|
String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
|
|
|
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
|
|
|
|
2017-04-12 08:18:29 +02:00
|
|
|
builder.addPathSegment("rest");
|
|
|
|
builder.addPathSegment(method + ".view");
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
int hash = (username + password).hashCode();
|
|
|
|
Pair<String, String> values = tokens.get(hash);
|
2018-03-25 03:28:28 +02:00
|
|
|
if (values == null) {
|
2016-12-18 18:41:30 +01:00
|
|
|
String salt = new BigInteger(130, getRandom()).toString(32);
|
|
|
|
String token = md5Hex(password + salt);
|
|
|
|
values = new Pair<>(salt, token);
|
|
|
|
tokens.put(hash, values);
|
|
|
|
}
|
|
|
|
|
2017-03-12 20:59:47 +01:00
|
|
|
builder.addQueryParameter("u", username);
|
|
|
|
builder.addQueryParameter("s", values.getFirst());
|
|
|
|
builder.addQueryParameter("t", values.getSecond());
|
|
|
|
builder.addQueryParameter("v", Constants.REST_PROTOCOL_VERSION_SUBSONIC);
|
|
|
|
builder.addQueryParameter("c", Constants.REST_CLIENT_ID);
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2019-03-01 00:04:52 +01:00
|
|
|
if (parameters != null) {
|
|
|
|
for (Map.Entry<String, String> parameter : parameters.entrySet()) {
|
|
|
|
builder.addQueryParameter(parameter.getKey(), parameter.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-12 20:59:47 +01:00
|
|
|
return builder.build().toString();
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static int getRestUrlHash(Context context) {
|
|
|
|
return getRestUrlHash(context, Util.getMostRecentActiveServer(context));
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static int getRestUrlHash(Context context, int instance) {
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
|
|
|
|
SharedPreferences prefs = Util.getPreferences(context);
|
|
|
|
builder.append(prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null));
|
|
|
|
builder.append(prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null));
|
|
|
|
|
|
|
|
return builder.toString().hashCode();
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
private static String getBlockTokenUsePref(Context context, int instance) {
|
2019-03-01 00:04:52 +01:00
|
|
|
return Constants.CACHE_BLOCK_TOKEN_USE + Util.getRestUrl(context, null, instance, false, null);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
public static void setBlockTokenUse(Context context, int instance) {
|
2018-03-24 20:25:12 +01:00
|
|
|
SharedPreferences.Editor editor = getPreferences(context).edit();
|
2018-03-25 03:28:28 +02:00
|
|
|
editor.putBoolean(getBlockTokenUsePref(context, instance), true);
|
2018-03-24 20:25:12 +01:00
|
|
|
editor.apply();
|
|
|
|
}
|
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
public static SharedPreferences getPreferences(Context context) {
|
|
|
|
return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
|
|
|
public static String getCacheName(Context context, String name, String id) {
|
2019-03-01 00:04:52 +01:00
|
|
|
String s = getRestUrl(context, null, getActiveServer(context), false, null) + id;
|
2018-03-24 20:25:12 +01:00
|
|
|
return name + "-" + s.hashCode() + ".ser";
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
public static String getCacheName(Context context) {
|
|
|
|
return getCacheName(context, "entryLookup", "");
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
public static String parseOfflineIDSearch(String id, String cacheLocation) {
|
2018-03-24 20:25:12 +01:00
|
|
|
// Try to get this info based off of tags first
|
|
|
|
String name = parseOfflineIDSearch(id);
|
2018-03-25 03:28:28 +02:00
|
|
|
if (name != null) {
|
2018-03-24 20:25:12 +01:00
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise go nuts trying to parse from file structure
|
|
|
|
name = id.replace(cacheLocation, "");
|
2018-03-25 03:28:28 +02:00
|
|
|
if (name.startsWith("/")) {
|
2018-03-24 20:25:12 +01:00
|
|
|
name = name.substring(1);
|
|
|
|
}
|
|
|
|
name = name.replace(".complete", "").replace(".partial", "");
|
|
|
|
int index = name.lastIndexOf(".");
|
|
|
|
name = index == -1 ? name : name.substring(0, index);
|
|
|
|
String[] details = name.split("/");
|
|
|
|
|
|
|
|
String title = details[details.length - 1];
|
2018-03-25 03:28:28 +02:00
|
|
|
if (index == -1) {
|
|
|
|
if (details.length > 1) {
|
2018-03-24 20:25:12 +01:00
|
|
|
String artist = "artist:\"" + details[details.length - 2] + "\"";
|
|
|
|
String simpleArtist = "artist:\"" + title + "\"";
|
|
|
|
title = "album:\"" + title + "\"";
|
2018-03-25 03:28:28 +02:00
|
|
|
if (details[details.length - 1].equals(details[details.length - 2])) {
|
2018-03-24 20:25:12 +01:00
|
|
|
name = title;
|
|
|
|
} else {
|
|
|
|
name = "(" + artist + " AND " + title + ")" + " OR " + simpleArtist;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name = "artist:\"" + title + "\" OR album:\"" + title + "\"";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
String artist;
|
2018-03-25 03:28:28 +02:00
|
|
|
if (details.length > 2) {
|
2018-03-24 20:25:12 +01:00
|
|
|
artist = "artist:\"" + details[details.length - 3] + "\"";
|
|
|
|
} else {
|
|
|
|
artist = "(artist:\"" + details[0] + "\" OR album:\"" + details[0] + "\")";
|
|
|
|
}
|
|
|
|
title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\"";
|
|
|
|
name = artist + " AND " + title;
|
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
private static String parseOfflineIDSearch(String id) {
|
2018-03-24 20:25:12 +01:00
|
|
|
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
|
|
|
File file = new File(id);
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (file.exists()) {
|
2018-03-24 20:25:12 +01:00
|
|
|
entry.loadMetadata(file);
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (entry.getArtist() != null) {
|
2018-03-24 20:25:12 +01:00
|
|
|
String title = file.getName();
|
|
|
|
title = title.replace(".complete", "").replace(".partial", "");
|
|
|
|
int index = title.lastIndexOf(".");
|
|
|
|
title = index == -1 ? title : title.substring(0, index);
|
|
|
|
title = title.substring(title.indexOf('-') + 1);
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
return "artist:\"" + entry.getArtist() + "\"" +
|
|
|
|
" AND title:\"" + title + "\"";
|
2018-03-24 20:25:12 +01:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isFirstLevelArtist(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_FIRST_LEVEL_ARTIST + getActiveServer(context), true);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static void toggleFirstLevelArtist(Context context) {
|
|
|
|
SharedPreferences prefs = Util.getPreferences(context);
|
|
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (prefs.getBoolean(Constants.PREFERENCES_KEY_FIRST_LEVEL_ARTIST + getActiveServer(context), true)) {
|
2018-03-24 20:25:12 +01:00
|
|
|
editor.putBoolean(Constants.PREFERENCES_KEY_FIRST_LEVEL_ARTIST + getActiveServer(context), false);
|
|
|
|
} else {
|
|
|
|
editor.putBoolean(Constants.PREFERENCES_KEY_FIRST_LEVEL_ARTIST + getActiveServer(context), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean shouldStartOnHeadphones(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_START_ON_HEADPHONES, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getSongPressAction(Context context) {
|
|
|
|
return getPreferences(context).getString(Constants.PREFERENCES_KEY_SONG_PRESS_ACTION, "all");
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
|
|
|
|
* <p/>
|
|
|
|
* This method buffers the input internally, so there is no need to use a
|
|
|
|
* <code>BufferedInputStream</code>.
|
|
|
|
*
|
|
|
|
* @param input the <code>InputStream</code> to read from
|
|
|
|
* @return the requested byte array
|
|
|
|
* @throws NullPointerException if the input is null
|
|
|
|
* @throws IOException if an I/O error occurs
|
|
|
|
*/
|
|
|
|
public static byte[] toByteArray(InputStream input) throws IOException {
|
|
|
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
|
|
copy(input, output);
|
|
|
|
return output.toByteArray();
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
private static void copy(InputStream input, OutputStream output)
|
2016-12-18 18:41:30 +01:00
|
|
|
throws IOException {
|
|
|
|
byte[] buffer = new byte[1024 * 4];
|
|
|
|
long count = 0;
|
|
|
|
int n;
|
|
|
|
while (-1 != (n = input.read(buffer))) {
|
|
|
|
output.write(buffer, 0, n);
|
|
|
|
count += n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
public static void renameFile(File from, File to) {
|
|
|
|
if (!from.renameTo(to)) {
|
2018-03-24 20:25:12 +01:00
|
|
|
Log.i(TAG, "Failed to rename " + from + " to " + to);
|
|
|
|
}
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
public static void close(Closeable closeable) {
|
|
|
|
try {
|
|
|
|
if (closeable != null) {
|
|
|
|
closeable.close();
|
|
|
|
}
|
|
|
|
} catch (Throwable x) {
|
|
|
|
// Ignored
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean delete(File file) {
|
|
|
|
if (file != null && file.exists()) {
|
|
|
|
if (!file.delete()) {
|
|
|
|
Log.w(TAG, "Failed to delete file " + file);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Log.i(TAG, "Deleted file " + file);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static void toast(Context context, int messageId) {
|
2016-12-18 18:41:30 +01:00
|
|
|
toast(context, messageId, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void toast(Context context, int messageId, boolean shortDuration) {
|
|
|
|
toast(context, context.getString(messageId), shortDuration);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void toast(Context context, String message) {
|
|
|
|
toast(context, message, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void toast(Context context, String message, boolean shortDuration) {
|
|
|
|
if (toast == null) {
|
|
|
|
toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
|
|
|
|
toast.setGravity(Gravity.CENTER, 0, 0);
|
|
|
|
} else {
|
|
|
|
toast.setText(message);
|
|
|
|
toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
|
|
|
|
}
|
|
|
|
toast.show();
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
public static void confirmDialog(Context context, DialogInterface.OnClickListener onClick) {
|
|
|
|
Util.confirmDialog(context, context.getResources().getString(R.string.common_delete).toLowerCase(), context.getResources().getString(R.string.common_confirm_message_cache), onClick);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static void confirmDialog(Context context, int action, String subject, DialogInterface.OnClickListener onClick) {
|
2018-03-25 03:28:28 +02:00
|
|
|
Util.confirmDialog(context, context.getResources().getString(action).toLowerCase(), subject, onClick);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static void confirmDialog(Context context, String action, String subject, DialogInterface.OnClickListener onClick) {
|
2018-03-24 20:25:12 +01:00
|
|
|
new AlertDialog.Builder(context)
|
2018-03-25 03:28:28 +02:00
|
|
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
|
|
.setTitle(R.string.common_confirm)
|
|
|
|
.setMessage(context.getResources().getString(R.string.common_confirm_message, action, subject))
|
|
|
|
.setPositiveButton(R.string.common_ok, onClick)
|
|
|
|
.setNegativeButton(R.string.common_cancel, null)
|
|
|
|
.show();
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a byte-count to a formatted string suitable for display to the user.
|
|
|
|
* For instance:
|
|
|
|
* <ul>
|
|
|
|
* <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
|
|
|
|
* <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
|
|
|
|
* <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
|
|
|
|
* </ul>
|
|
|
|
* This method assumes that 1 KB is 1024 bytes.
|
|
|
|
* To get a localized string, please use formatLocalizedBytes instead.
|
|
|
|
*
|
|
|
|
* @param byteCount The number of bytes.
|
|
|
|
* @return The formatted string.
|
|
|
|
*/
|
|
|
|
public static synchronized String formatBytes(long byteCount) {
|
|
|
|
|
|
|
|
// More than 1 GB?
|
|
|
|
if (byteCount >= 1024 * 1024 * 1024) {
|
2018-03-25 03:28:28 +02:00
|
|
|
return GIGA_BYTE_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 MB?
|
|
|
|
if (byteCount >= 1024 * 1024) {
|
2018-03-25 03:28:28 +02:00
|
|
|
return MEGA_BYTE_FORMAT.format((double) byteCount / (1024 * 1024));
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 KB?
|
|
|
|
if (byteCount >= 1024) {
|
2018-03-25 03:28:28 +02:00
|
|
|
return KILO_BYTE_FORMAT.format((double) byteCount / 1024);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return byteCount + " B";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a byte-count to a formatted string suitable for display to the user.
|
|
|
|
* For instance:
|
|
|
|
* <ul>
|
|
|
|
* <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
|
|
|
|
* <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
|
|
|
|
* <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
|
|
|
|
* </ul>
|
|
|
|
* This method assumes that 1 KB is 1024 bytes.
|
|
|
|
* This version of the method returns a localized string.
|
|
|
|
*
|
|
|
|
* @param byteCount The number of bytes.
|
|
|
|
* @return The formatted string.
|
|
|
|
*/
|
|
|
|
public static synchronized String formatLocalizedBytes(long byteCount, Context context) {
|
|
|
|
|
|
|
|
// More than 1 GB?
|
|
|
|
if (byteCount >= 1024 * 1024 * 1024) {
|
|
|
|
if (GIGA_BYTE_LOCALIZED_FORMAT == null) {
|
|
|
|
GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_gigabyte));
|
|
|
|
}
|
|
|
|
|
|
|
|
return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
|
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 MB?
|
|
|
|
if (byteCount >= 1024 * 1024) {
|
|
|
|
if (MEGA_BYTE_LOCALIZED_FORMAT == null) {
|
|
|
|
MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_megabyte));
|
|
|
|
}
|
|
|
|
|
|
|
|
return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024));
|
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 KB?
|
|
|
|
if (byteCount >= 1024) {
|
|
|
|
if (KILO_BYTE_LOCALIZED_FORMAT == null) {
|
|
|
|
KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_kilobyte));
|
|
|
|
}
|
|
|
|
|
|
|
|
return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (BYTE_LOCALIZED_FORMAT == null) {
|
|
|
|
BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_byte));
|
|
|
|
}
|
|
|
|
|
|
|
|
return BYTE_LOCALIZED_FORMAT.format((double) byteCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String formatDuration(Integer seconds) {
|
|
|
|
if (seconds == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
int hours = seconds / 3600;
|
2016-12-18 18:41:30 +01:00
|
|
|
int minutes = (seconds / 60) % 60;
|
|
|
|
int secs = seconds % 60;
|
|
|
|
|
|
|
|
StringBuilder builder = new StringBuilder(7);
|
2018-03-25 03:28:28 +02:00
|
|
|
if (hours > 0) {
|
2018-03-24 20:25:12 +01:00
|
|
|
builder.append(hours).append(":");
|
2018-03-25 03:28:28 +02:00
|
|
|
if (minutes < 10) {
|
2018-03-24 20:25:12 +01:00
|
|
|
builder.append("0");
|
|
|
|
}
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
builder.append(minutes).append(":");
|
|
|
|
if (secs < 10) {
|
|
|
|
builder.append("0");
|
|
|
|
}
|
|
|
|
builder.append(secs);
|
|
|
|
return builder.toString();
|
|
|
|
}
|
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static String formatBoolean(Context context, boolean value) {
|
|
|
|
return context.getResources().getString(value ? R.string.common_true : R.string.common_false);
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
public static boolean equals(Object object1, Object object2) {
|
2018-03-25 03:28:28 +02:00
|
|
|
return object1 == object2 || !(object1 == null || object2 == null) && object1.equals(object2);
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
|
|
|
|
* The returned array will be double the length of the passed array, as it takes two characters to represent any
|
|
|
|
* given byte.
|
|
|
|
*
|
|
|
|
* @param data Bytes to convert to hexadecimal characters.
|
|
|
|
* @return A string containing hexadecimal characters.
|
|
|
|
*/
|
2018-03-25 03:28:28 +02:00
|
|
|
private static String hexEncode(byte[] data) {
|
2016-12-18 18:41:30 +01:00
|
|
|
int length = data.length;
|
|
|
|
char[] out = new char[length << 1];
|
|
|
|
// two characters form the hex value.
|
|
|
|
for (int i = 0, j = 0; i < length; i++) {
|
|
|
|
out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
|
|
|
|
out[j++] = HEX_DIGITS[0x0F & data[i]];
|
|
|
|
}
|
|
|
|
return new String(out);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates the MD5 digest and returns the value as a 32 character hex string.
|
|
|
|
*
|
|
|
|
* @param s Data to digest.
|
|
|
|
* @return MD5 digest as a hex string.
|
|
|
|
*/
|
|
|
|
public static String md5Hex(String s) {
|
|
|
|
if (s == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
2019-01-22 06:08:49 +01:00
|
|
|
return hexEncode(md5.digest(s.getBytes(StandardCharsets.UTF_8)));
|
2016-12-18 18:41:30 +01:00
|
|
|
} catch (Exception x) {
|
|
|
|
throw new RuntimeException(x.getMessage(), x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
|
|
|
// Raw height and width of image
|
|
|
|
final int height = options.outHeight;
|
|
|
|
final int width = options.outWidth;
|
|
|
|
int inSampleSize = 1;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
if (height > reqHeight || width > reqWidth) {
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
// Calculate ratios of height and width to requested height and
|
|
|
|
// width
|
|
|
|
final int heightRatio = Math.round((float) height / (float) reqHeight);
|
|
|
|
final int widthRatio = Math.round((float) width / (float) reqWidth);
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
// Choose the smallest ratio as inSampleSize value, this will
|
|
|
|
// guarantee
|
|
|
|
// a final image with both dimensions larger than or equal to the
|
|
|
|
// requested height and width.
|
|
|
|
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
return inSampleSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getScaledHeight(double height, double width, int newWidth) {
|
|
|
|
// Try to keep correct aspect ratio of the original image, do not force a square
|
|
|
|
double aspectRatio = height / width;
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
// Assume the size given refers to the width of the image, so calculate the new height using
|
|
|
|
// the previously determined aspect ratio
|
|
|
|
return (int) Math.round(newWidth * aspectRatio);
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static int getScaledHeight(Bitmap bitmap, int width) {
|
|
|
|
return Util.getScaledHeight((double) bitmap.getHeight(), (double) bitmap.getWidth(), width);
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static int getStringDistance(CharSequence s, CharSequence t) {
|
|
|
|
if (s == null || t == null) {
|
|
|
|
throw new IllegalArgumentException("Strings must not be null");
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (t.toString().toLowerCase().contains(s.toString().toLowerCase())) {
|
2018-03-24 20:25:12 +01:00
|
|
|
return 1;
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
int n = s.length();
|
2018-03-24 20:25:12 +01:00
|
|
|
int m = t.length();
|
|
|
|
|
|
|
|
if (n == 0) {
|
|
|
|
return m;
|
|
|
|
} else if (m == 0) {
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n > m) {
|
|
|
|
final CharSequence tmp = s;
|
|
|
|
s = t;
|
|
|
|
t = tmp;
|
|
|
|
n = m;
|
|
|
|
m = t.length();
|
|
|
|
}
|
|
|
|
|
|
|
|
int p[] = new int[n + 1];
|
|
|
|
int d[] = new int[n + 1];
|
|
|
|
int _d[];
|
|
|
|
|
|
|
|
int i;
|
|
|
|
int j;
|
|
|
|
char t_j;
|
|
|
|
int cost;
|
|
|
|
|
|
|
|
for (i = 0; i <= n; i++) {
|
|
|
|
p[i] = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 1; j <= m; j++) {
|
|
|
|
t_j = t.charAt(j - 1);
|
|
|
|
d[0] = j;
|
|
|
|
|
|
|
|
for (i = 1; i <= n; i++) {
|
|
|
|
cost = s.charAt(i - 1) == t_j ? 0 : 1;
|
|
|
|
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
|
|
|
|
}
|
|
|
|
|
|
|
|
_d = p;
|
|
|
|
p = d;
|
|
|
|
d = _d;
|
|
|
|
}
|
|
|
|
|
|
|
|
return p[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isNetworkConnected(Context context) {
|
|
|
|
return isNetworkConnected(context, false);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static boolean isNetworkConnected(Context context, boolean streaming) {
|
2016-12-18 18:41:30 +01:00
|
|
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
|
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
|
|
|
boolean connected = networkInfo != null && networkInfo.isConnected();
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
if (streaming) {
|
2018-03-24 20:25:12 +01:00
|
|
|
boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
|
|
|
|
boolean wifiRequired = isWifiRequiredForDownload(context);
|
|
|
|
|
|
|
|
return connected && (!wifiRequired || wifiConnected);
|
|
|
|
} else {
|
|
|
|
return connected;
|
|
|
|
}
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static boolean isWifiConnected(Context context) {
|
2018-03-24 20:25:12 +01:00
|
|
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
|
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
|
|
|
boolean connected = networkInfo != null && networkInfo.isConnected();
|
|
|
|
return connected && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static String getSSID(Context context) {
|
|
|
|
if (isWifiConnected(context)) {
|
|
|
|
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
|
|
|
if (wifiManager.getConnectionInfo() != null && wifiManager.getConnectionInfo().getSSID() != null) {
|
|
|
|
return wifiManager.getConnectionInfo().getSSID().replace("\"", "");
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
public static boolean isExternalStoragePresent() {
|
|
|
|
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
|
|
|
}
|
|
|
|
|
2018-03-24 20:25:12 +01:00
|
|
|
public static boolean isAllowedToDownload(Context context) {
|
|
|
|
return isNetworkConnected(context, true) && !isOffline(context);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
public static boolean isWifiRequiredForDownload(Context context) {
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void info(Context context, int titleId, int messageId) {
|
2018-03-25 03:28:28 +02:00
|
|
|
showDialog(context, titleId, messageId);
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
private static void showDialog(Context context, int titleId, int messageId) {
|
|
|
|
showDialog(context, context.getResources().getString(titleId), context.getResources().getString(messageId));
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static void showDialog(Context context, String title, String message) {
|
2018-03-24 20:25:12 +01:00
|
|
|
SpannableString ss = new SpannableString(message);
|
2018-03-25 03:28:28 +02:00
|
|
|
Linkify.addLinks(ss, Linkify.ALL);
|
2018-03-24 20:25:12 +01:00
|
|
|
|
|
|
|
AlertDialog dialog = new AlertDialog.Builder(context)
|
2018-03-25 03:28:28 +02:00
|
|
|
.setIcon(android.R.drawable.ic_dialog_info)
|
|
|
|
.setTitle(title)
|
|
|
|
.setMessage(ss)
|
|
|
|
.setPositiveButton(R.string.common_ok, (dialog1, i) -> dialog1.dismiss())
|
|
|
|
.show();
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static void showDetailsDialog(Context context, @StringRes int title, List<Integer> headers, List<String> details) {
|
|
|
|
List<String> headerStrings = new ArrayList<>();
|
2018-03-25 03:28:28 +02:00
|
|
|
for (@StringRes Integer res : headers) {
|
2018-03-24 20:25:12 +01:00
|
|
|
headerStrings.add(context.getResources().getString(res));
|
|
|
|
}
|
|
|
|
showDetailsDialog(context, context.getResources().getString(title), headerStrings, details);
|
|
|
|
}
|
2018-03-25 03:28:28 +02:00
|
|
|
|
|
|
|
private static void showDetailsDialog(Context context, String title, List<String> headers, final List<String> details) {
|
2018-03-24 20:25:12 +01:00
|
|
|
ListView listView = new ListView(context);
|
2018-03-25 03:28:28 +02:00
|
|
|
listView.setAdapter(new DetailsAdapter(context, headers, details));
|
2018-03-24 20:25:12 +01:00
|
|
|
listView.setDivider(null);
|
|
|
|
listView.setScrollbarFadingEnabled(false);
|
|
|
|
|
|
|
|
// Let the user long-click on a row to copy its value to the clipboard
|
|
|
|
final Context contextRef = context;
|
2018-03-25 03:28:28 +02:00
|
|
|
listView.setOnItemLongClickListener((parent, view, pos, id) -> {
|
|
|
|
TextView nameView = view.findViewById(R.id.detail_name);
|
|
|
|
TextView detailsView = view.findViewById(R.id.detail_value);
|
|
|
|
if (nameView == null || detailsView == null) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
CharSequence name = nameView.getText();
|
|
|
|
CharSequence value = detailsView.getText();
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
ClipboardManager clipboard = (ClipboardManager) contextRef.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
|
|
ClipData clip = ClipData.newPlainText(name, value);
|
|
|
|
clipboard.setPrimaryClip(clip);
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
toast(contextRef, "Copied " + name + " to clipboard");
|
2018-03-24 20:25:12 +01:00
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
return true;
|
2018-03-24 20:25:12 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
new AlertDialog.Builder(context)
|
|
|
|
// .setIcon(android.R.drawable.ic_dialog_info)
|
|
|
|
.setTitle(title)
|
|
|
|
.setView(listView)
|
2018-03-25 03:28:28 +02:00
|
|
|
.setPositiveButton(R.string.common_close, (dialog, i) -> dialog.dismiss())
|
2018-03-24 20:25:12 +01:00
|
|
|
.show();
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
|
|
|
|
public static void sleepQuietly(long millis) {
|
|
|
|
try {
|
|
|
|
Thread.sleep(millis);
|
|
|
|
} catch (InterruptedException x) {
|
|
|
|
Log.w(TAG, "Interrupted from sleep.", x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) {
|
|
|
|
currentActivity.startActivity(intent);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) {
|
2018-03-25 03:28:28 +02:00
|
|
|
return new BitmapDrawable(context.getResources(), bitmap);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static void registerMediaButtonEventReceiver(Context context) {
|
|
|
|
|
|
|
|
// Only do it if enabled in the settings.
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true);
|
|
|
|
|
|
|
|
if (enabled) {
|
2018-03-25 03:28:28 +02:00
|
|
|
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
|
|
|
|
audioManager.registerMediaButtonEventReceiver(componentName);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void unregisterMediaButtonEventReceiver(Context context) {
|
2018-03-25 03:28:28 +02:00
|
|
|
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
|
|
|
|
audioManager.unregisterMediaButtonEventReceiver(componentName);
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
|
|
|
|
public static void requestAudioFocus(final Context context) {
|
|
|
|
if (focusListener == null) {
|
|
|
|
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
audioManager.requestAudioFocus(focusListener = new OnAudioFocusChangeListener() {
|
|
|
|
public void onAudioFocusChange(int focusChange) {
|
2018-03-25 03:28:28 +02:00
|
|
|
DownloadService downloadService = (DownloadService) context;
|
2018-04-25 01:58:40 +02:00
|
|
|
switch (focusChange) {
|
|
|
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
|
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
|
|
if (downloadService.getPlayerState() == PlayerState.STARTED) {
|
|
|
|
Log.i(TAG, "Temporary loss of focus");
|
|
|
|
SharedPreferences prefs = getPreferences(context);
|
|
|
|
int lossPref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1"));
|
|
|
|
if (lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) {
|
|
|
|
lowerFocus = true;
|
|
|
|
downloadService.setVolume(0.1f);
|
|
|
|
} else if (lossPref == 0 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) {
|
|
|
|
pauseFocus = true;
|
|
|
|
downloadService.pause(true);
|
|
|
|
}
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
2018-04-25 01:58:40 +02:00
|
|
|
break;
|
|
|
|
case AudioManager.AUDIOFOCUS_GAIN:
|
|
|
|
if (pauseFocus) {
|
|
|
|
pauseFocus = false;
|
|
|
|
downloadService.start();
|
|
|
|
}
|
|
|
|
if (lowerFocus) {
|
|
|
|
lowerFocus = false;
|
|
|
|
downloadService.setVolume(1.0f);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case AudioManager.AUDIOFOCUS_LOSS:
|
|
|
|
Log.i(TAG, "Permanently lost focus");
|
|
|
|
focusListener = null;
|
|
|
|
downloadService.pause();
|
|
|
|
audioManager.abandonAudioFocus(this);
|
|
|
|
break;
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-18 18:41:30 +01:00
|
|
|
/**
|
|
|
|
* <p>Broadcasts the given song info as the new song being played.</p>
|
|
|
|
*/
|
|
|
|
public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) {
|
2018-03-24 20:25:12 +01:00
|
|
|
try {
|
|
|
|
Intent intent = new Intent(EVENT_META_CHANGED);
|
|
|
|
Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);
|
|
|
|
|
|
|
|
if (song != null) {
|
|
|
|
intent.putExtra("title", song.getTitle());
|
|
|
|
intent.putExtra("artist", song.getArtist());
|
|
|
|
intent.putExtra("album", song.getAlbum());
|
|
|
|
|
|
|
|
File albumArtFile = FileUtil.getAlbumArtFile(context, song);
|
|
|
|
intent.putExtra("coverart", albumArtFile.getAbsolutePath());
|
|
|
|
avrcpIntent.putExtra("playing", true);
|
|
|
|
} else {
|
|
|
|
intent.putExtra("title", "");
|
|
|
|
intent.putExtra("artist", "");
|
|
|
|
intent.putExtra("album", "");
|
|
|
|
intent.putExtra("coverart", "");
|
|
|
|
avrcpIntent.putExtra("playing", false);
|
|
|
|
}
|
|
|
|
addTrackInfo(context, song, avrcpIntent);
|
|
|
|
|
|
|
|
context.sendBroadcast(intent);
|
|
|
|
context.sendBroadcast(avrcpIntent);
|
2018-03-25 03:28:28 +02:00
|
|
|
} catch (Exception e) {
|
2018-03-24 20:25:12 +01:00
|
|
|
Log.e(TAG, "Failed to broadcastNewTrackInfo", e);
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* <p>Broadcasts the given player state as the one being set.</p>
|
|
|
|
*/
|
|
|
|
public static void broadcastPlaybackStatusChange(Context context, MusicDirectory.Entry song, PlayerState state) {
|
2018-03-24 20:25:12 +01:00
|
|
|
try {
|
|
|
|
Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
|
|
|
|
Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case STARTED:
|
|
|
|
intent.putExtra("state", "play");
|
|
|
|
avrcpIntent.putExtra("playing", true);
|
|
|
|
break;
|
|
|
|
case STOPPED:
|
|
|
|
intent.putExtra("state", "stop");
|
|
|
|
avrcpIntent.putExtra("playing", false);
|
|
|
|
break;
|
|
|
|
case PAUSED:
|
|
|
|
intent.putExtra("state", "pause");
|
|
|
|
avrcpIntent.putExtra("playing", false);
|
|
|
|
break;
|
|
|
|
case PREPARED:
|
|
|
|
// Only send quick pause event for samsung devices, causes issues for others
|
2018-03-25 03:28:28 +02:00
|
|
|
if (Build.MANUFACTURER.toLowerCase().contains("samsung")) {
|
2018-03-24 20:25:12 +01:00
|
|
|
avrcpIntent.putExtra("playing", false);
|
|
|
|
} else {
|
|
|
|
return; // Don't broadcast anything
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case COMPLETED:
|
|
|
|
intent.putExtra("state", "complete");
|
|
|
|
avrcpIntent.putExtra("playing", false);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return; // No need to broadcast.
|
|
|
|
}
|
|
|
|
addTrackInfo(context, song, avrcpIntent);
|
|
|
|
|
|
|
|
if (state != PlayerState.PREPARED) {
|
|
|
|
context.sendBroadcast(intent);
|
|
|
|
}
|
|
|
|
context.sendBroadcast(avrcpIntent);
|
2018-03-25 03:28:28 +02:00
|
|
|
} catch (Exception e) {
|
2018-03-24 20:25:12 +01:00
|
|
|
Log.e(TAG, "Failed to broadcastPlaybackStatusChange", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void addTrackInfo(Context context, MusicDirectory.Entry song, Intent intent) {
|
|
|
|
if (song != null) {
|
2018-03-25 03:28:28 +02:00
|
|
|
DownloadService downloadService = (DownloadService) context;
|
2018-03-24 20:25:12 +01:00
|
|
|
File albumArtFile = FileUtil.getAlbumArtFile(context, song);
|
|
|
|
|
|
|
|
intent.putExtra("track", song.getTitle());
|
|
|
|
intent.putExtra("artist", song.getArtist());
|
|
|
|
intent.putExtra("album", song.getAlbum());
|
|
|
|
intent.putExtra("ListSize", (long) downloadService.getSongs().size());
|
|
|
|
intent.putExtra("id", (long) downloadService.getCurrentPlayingIndex() + 1);
|
|
|
|
intent.putExtra("duration", (long) downloadService.getPlayerDuration());
|
|
|
|
intent.putExtra("position", (long) downloadService.getPlayerPosition());
|
|
|
|
intent.putExtra("coverart", albumArtFile.getAbsolutePath());
|
2018-03-25 03:28:28 +02:00
|
|
|
intent.putExtra("package", "net.nullsum.audinaut");
|
2018-03-24 20:25:12 +01:00
|
|
|
} else {
|
|
|
|
intent.putExtra("track", "");
|
|
|
|
intent.putExtra("artist", "");
|
|
|
|
intent.putExtra("album", "");
|
|
|
|
intent.putExtra("ListSize", (long) 0);
|
|
|
|
intent.putExtra("id", (long) 0);
|
|
|
|
intent.putExtra("duration", (long) 0);
|
|
|
|
intent.putExtra("position", (long) 0);
|
|
|
|
intent.putExtra("coverart", "");
|
2018-03-25 03:28:28 +02:00
|
|
|
intent.putExtra("package", "net.nullsum.audinaut");
|
2018-03-24 20:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static WifiManager.WifiLock createWifiLock(Context context, String tag) {
|
|
|
|
WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
|
|
|
return wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, tag);
|
|
|
|
}
|
|
|
|
|
2018-03-25 03:28:28 +02:00
|
|
|
private static Random getRandom() {
|
|
|
|
if (random == null) {
|
2018-03-24 20:25:12 +01:00
|
|
|
random = new SecureRandom();
|
|
|
|
}
|
|
|
|
|
|
|
|
return random;
|
|
|
|
}
|
2016-12-18 18:41:30 +01:00
|
|
|
}
|