commit
1dda7e9600
|
@ -0,0 +1,29 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
Version 1.0.1
|
||||
-------------
|
||||
- Fixed bug with In-app purchase key that was crashing Settings screen
|
||||
|
||||
Version 1.0.0
|
||||
-------------
|
||||
- New options to sort movies and tv shows
|
||||
- Bulgarian translation (by NEOhidra)
|
||||
- German translation (by jonas2515)
|
||||
|
||||
Version 0.9.2
|
||||
-------------
|
||||
- Added new actions in remote: update/clean library and toggle fullscreen
|
||||
- French translation (thanks Kowalski!)
|
||||
- Bug fixes and visual tweaks
|
||||
|
||||
Version 0.9.1
|
||||
-------------
|
||||
- Improved library sync;
|
||||
- Automatically switch to remove after media start;
|
||||
- Visual tweaks.
|
||||
|
||||
Version 0.9.0
|
||||
-------------
|
||||
- First version
|
||||
|
|
@ -35,6 +35,7 @@ Credits
|
|||
- French - Kowalski
|
||||
- Bulgarian - NEOhidra
|
||||
- German - jonas2515
|
||||
- Italian - Enrico Strocchi
|
||||
|
||||
Links
|
||||
-----
|
||||
|
|
|
@ -8,8 +8,8 @@ android {
|
|||
applicationId "com.syncedsynapse.kore2"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 21
|
||||
versionCode 5
|
||||
versionName "1.0.0"
|
||||
versionCode 6
|
||||
versionName "1.0.1"
|
||||
|
||||
buildConfigField("String", "IAP_KEY", "\"${rootProject.property("IAP_KEY")}\"")
|
||||
}
|
||||
|
|
|
@ -42,6 +42,10 @@
|
|||
<!-- Services -->
|
||||
<service android:name="com.syncedsynapse.kore2.service.LibrarySyncService"
|
||||
android:exported="false"/>
|
||||
<service android:name="com.syncedsynapse.kore2.service.NotificationService"
|
||||
android:exported="false"/>
|
||||
<service android:name="com.syncedsynapse.kore2.service.IntentActionsService"
|
||||
android:exported="false"/>
|
||||
|
||||
</application>
|
||||
|
||||
|
|
|
@ -50,48 +50,64 @@ public class Settings {
|
|||
// Maximum pictures to show on cast list (-1 to show all)
|
||||
public static final int DEFAULT_MAX_CAST_PICTURES = 12;
|
||||
|
||||
// Sort orders
|
||||
public static final int SORT_BY_NAME = 0,
|
||||
SORT_BY_DATE_ADDED = 1;
|
||||
|
||||
/**
|
||||
* Default Shared Preferences keys.
|
||||
* Preferences keys.
|
||||
* These settings are automatically managed by the Preferences mechanism.
|
||||
* Make sure these are the same as in preferences.xml
|
||||
*/
|
||||
|
||||
// Theme
|
||||
public static final String KEY_PREF_THEME = "pref_theme";
|
||||
public static final String KEY_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START =
|
||||
"pref_switch_to_remote_after_media_start";
|
||||
public static final String DEFAULT_PREF_THEME = "0";
|
||||
|
||||
// Switch to remote
|
||||
public static final String KEY_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START = "pref_switch_to_remote_after_media_start";
|
||||
public static final boolean DEFAULT_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START = true;
|
||||
|
||||
// Show notifications
|
||||
public static final String KEY_PREF_SHOW_NOTIFICATION = "pref_show_notification";
|
||||
public static final boolean DEFAULT_PREF_SHOW_NOTIFICATION = false;
|
||||
|
||||
// Other keys used in preferences.xml
|
||||
public static final String KEY_PREF_ABOUT = "pref_about";
|
||||
public static final String KEY_PREF_COFFEE = "pref_coffee";
|
||||
|
||||
// Filter watched movies on movie list
|
||||
public static final String KEY_PREF_MOVIES_FILTER_HIDE_WATCHED = "movies_filter_hide_watched";
|
||||
public static final boolean DEFAULT_PREF_MOVIES_FILTER_HIDE_WATCHED = false;
|
||||
|
||||
// Sort order on movies
|
||||
public static final String KEY_PREF_MOVIES_SORT_ORDER = "movies_sort_order";
|
||||
public static final int DEFAULT_PREF_MOVIES_SORT_ORDER = SORT_BY_NAME;
|
||||
|
||||
// Ignore articles on movie sorting
|
||||
public static final String KEY_PREF_MOVIES_IGNORE_PREFIXES = "movies_ignore_prefixes";
|
||||
public static final boolean DEFAULT_PREF_MOVIES_IGNORE_PREFIXES = false;
|
||||
|
||||
// Filter watched tv shows on tvshow list
|
||||
public static final String KEY_PREF_TVSHOWS_FILTER_HIDE_WATCHED = "tvshows_filter_hide_watched";
|
||||
public static final String KEY_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED = "tvshow_episodes_filter_hide_watched";
|
||||
// Sort order on tv shows
|
||||
public static final String KEY_PREF_TVSHOWS_SORT_ORDER = "tvshows_sort_order";
|
||||
// Ignore articles on tv show sorting
|
||||
public static final String KEY_PREF_TVSHOWS_IGNORE_PREFIXES = "tvshows_ignore_prefixes";
|
||||
|
||||
// Defaults for the preferences
|
||||
public static final String DEFAULT_PREF_THEME = "0";
|
||||
public static final boolean DEFAULT_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START = true;
|
||||
public static final boolean DEFAULT_PREF_MOVIES_FILTER_HIDE_WATCHED = false;
|
||||
public static final boolean DEFAULT_PREF_TVSHOWS_FILTER_HIDE_WATCHED = false;
|
||||
|
||||
// Filter watched episodes on episodes list
|
||||
public static final String KEY_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED = "tvshow_episodes_filter_hide_watched";
|
||||
public static final boolean DEFAULT_PREF_TVSHOW_EPISODES_FILTER_HIDE_WATCHED = false;
|
||||
|
||||
// Sort orders
|
||||
public static final int SORT_BY_NAME = 0,
|
||||
SORT_BY_DATE_ADDED = 1;
|
||||
public static final int DEFAULT_PREF_MOVIES_SORT_ORDER = SORT_BY_NAME;
|
||||
// Sort order on tv shows
|
||||
public static final String KEY_PREF_TVSHOWS_SORT_ORDER = "tvshows_sort_order";
|
||||
public static final int DEFAULT_PREF_TVSHOWS_SORT_ORDER = SORT_BY_NAME;
|
||||
|
||||
public static final boolean DEFAULT_PREF_MOVIES_IGNORE_PREFIXES = false;
|
||||
// Ignore articles on tv show sorting
|
||||
public static final String KEY_PREF_TVSHOWS_IGNORE_PREFIXES = "tvshows_ignore_prefixes";
|
||||
public static final boolean DEFAULT_PREF_TVSHOWS_IGNORE_PREFIXES = false;
|
||||
|
||||
// Use hardware volume keys to control volume
|
||||
public static final String KEY_PREF_USE_HARDWARE_VOLUME_KEYS = "pref_use_hardware_volume_keys";
|
||||
public static final boolean DEFAULT_PREF_USE_HARDWARE_VOLUME_KEYS = true;
|
||||
|
||||
|
||||
// Singleton instance
|
||||
private static Settings instance = null;
|
||||
|
|
|
@ -107,6 +107,11 @@ public class HostConnectionObserver
|
|||
* Notifies that XBMC has requested input
|
||||
*/
|
||||
public void inputOnInputRequested(String title, String type, String value);
|
||||
|
||||
/**
|
||||
* Notifies the observer that it this is stopping
|
||||
*/
|
||||
public void observerOnStopObserving();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +196,7 @@ public class HostConnectionObserver
|
|||
* Registers a new observer that will be notified about player events
|
||||
* @param observer Observer
|
||||
*/
|
||||
public synchronized void registerPlayerObserver(PlayerEventsObserver observer) {
|
||||
public void registerPlayerObserver(PlayerEventsObserver observer, boolean replyImmediately) {
|
||||
if (this.connection == null)
|
||||
return;
|
||||
|
||||
|
@ -199,6 +204,8 @@ public class HostConnectionObserver
|
|||
playerEventsObservers.add(observer);
|
||||
// observerHandlerMap.put(observer, new Handler());
|
||||
|
||||
if (replyImmediately) replyWithLastResult(observer);
|
||||
|
||||
if (playerEventsObservers.size() == 1) {
|
||||
// If this is the first observer, start checking through HTTP or register us
|
||||
// as a connection observer, which we will pass to the "real" observer
|
||||
|
@ -218,8 +225,7 @@ public class HostConnectionObserver
|
|||
* Unregisters a previously registered observer
|
||||
* @param observer Observer to unregister
|
||||
*/
|
||||
public synchronized void unregisterPlayerObserver(PlayerEventsObserver observer) {
|
||||
// Remove this observer and its associated handler
|
||||
public void unregisterPlayerObserver(PlayerEventsObserver observer) {
|
||||
playerEventsObservers.remove(observer);
|
||||
// observerHandlerMap.remove(observer);
|
||||
|
||||
|
@ -244,8 +250,10 @@ public class HostConnectionObserver
|
|||
/**
|
||||
* Unregisters all observers
|
||||
*/
|
||||
public void unregisterAllObservers() {
|
||||
// observerHandlerMap.clear();
|
||||
public void stopObserving() {
|
||||
for (final PlayerEventsObserver observer : playerEventsObservers)
|
||||
observer.observerOnStopObserving();
|
||||
|
||||
playerEventsObservers.clear();
|
||||
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
|
@ -293,25 +301,33 @@ public class HostConnectionObserver
|
|||
* The {@link HostConnection.SystemNotificationsObserver} interface methods
|
||||
*/
|
||||
public void onQuit(System.OnQuit notification) {
|
||||
for (final PlayerEventsObserver observer : playerEventsObservers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
observer.systemOnQuit();
|
||||
}
|
||||
}
|
||||
|
||||
public void onRestart(System.OnRestart notification) {
|
||||
for (final PlayerEventsObserver observer : playerEventsObservers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
observer.systemOnQuit();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSleep(System.OnSleep notification) {
|
||||
for (final PlayerEventsObserver observer : playerEventsObservers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
observer.systemOnQuit();
|
||||
}
|
||||
}
|
||||
|
||||
public void onInputRequested(Input.OnInputRequested notification) {
|
||||
for (final PlayerEventsObserver observer : playerEventsObservers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
observer.inputOnInputRequested(notification.title, notification.type, notification.value);
|
||||
}
|
||||
}
|
||||
|
@ -467,7 +483,9 @@ public class HostConnectionObserver
|
|||
lastErrorCode = errorCode;
|
||||
lastErrorDescription = description;
|
||||
forceReply = false;
|
||||
for (final PlayerEventsObserver observer : observers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
notifyConnectionError(errorCode, description, observer);
|
||||
}
|
||||
}
|
||||
|
@ -503,7 +521,9 @@ public class HostConnectionObserver
|
|||
(lastCallResult != PlayerEventsObserver.PLAYER_IS_STOPPED)) {
|
||||
lastCallResult = PlayerEventsObserver.PLAYER_IS_STOPPED;
|
||||
forceReply = false;
|
||||
for (final PlayerEventsObserver observer : observers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
notifyNothingIsPlaying(observer);
|
||||
}
|
||||
}
|
||||
|
@ -543,7 +563,9 @@ public class HostConnectionObserver
|
|||
lastGetPropertiesResult = getPropertiesResult;
|
||||
lastGetItemResult = getItemResult;
|
||||
forceReply = false;
|
||||
for (final PlayerEventsObserver observer : observers) {
|
||||
// Copy list to prevent ConcurrentModificationExceptions
|
||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
||||
for (final PlayerEventsObserver observer : allObservers) {
|
||||
notifySomethingIsPlaying(getActivePlayersResult, getPropertiesResult, getItemResult, observer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ import android.net.Uri;
|
|||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.syncedsynapse.kore2.Settings;
|
||||
import com.syncedsynapse.kore2.provider.MediaContract;
|
||||
import com.syncedsynapse.kore2.jsonrpc.HostConnection;
|
||||
import com.syncedsynapse.kore2.provider.MediaContract;
|
||||
import com.syncedsynapse.kore2.utils.BasicAuthPicassoDownloader;
|
||||
import com.syncedsynapse.kore2.utils.LogUtils;
|
||||
|
||||
|
@ -375,7 +375,7 @@ public class HostManager {
|
|||
*/
|
||||
private void releaseCurrentHost() {
|
||||
if (currentHostConnectionObserver != null) {
|
||||
currentHostConnectionObserver.unregisterAllObservers();
|
||||
currentHostConnectionObserver.stopObserving();
|
||||
currentHostConnectionObserver = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package com.syncedsynapse.kore2.jsonrpc;
|
||||
|
||||
import android.os.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.util.Base64;
|
||||
|
||||
|
@ -24,7 +24,8 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.syncedsynapse.kore2.host.HostInfo;
|
||||
import com.syncedsynapse.kore2.jsonrpc.notification.*;
|
||||
import com.syncedsynapse.kore2.jsonrpc.notification.Input;
|
||||
import com.syncedsynapse.kore2.jsonrpc.notification.Player;
|
||||
import com.syncedsynapse.kore2.jsonrpc.notification.System;
|
||||
import com.syncedsynapse.kore2.utils.LogUtils;
|
||||
|
||||
|
@ -37,7 +38,6 @@ import java.net.HttpURLConnection;
|
|||
import java.net.InetSocketAddress;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -134,31 +134,33 @@ public class HostConnection {
|
|||
|
||||
private ExecutorService executorService;
|
||||
|
||||
private final int connectionTimeout;
|
||||
private final int connectTimeout;
|
||||
|
||||
private static final int DEFAULT_TIMEOUT = 10000; // ms
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms
|
||||
|
||||
private static final int TCP_READ_TIMEOUT = 30000; // ms
|
||||
|
||||
/**
|
||||
* Creates a new host connection
|
||||
* @param hostInfo Host info object
|
||||
*/
|
||||
public HostConnection(final HostInfo hostInfo) {
|
||||
this(hostInfo, DEFAULT_TIMEOUT);
|
||||
this(hostInfo, DEFAULT_CONNECT_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new host connection
|
||||
* @param hostInfo Host info object
|
||||
* @param connectionTimeout Connection timeout in ms
|
||||
* @param connectTimeout Connection timeout in ms
|
||||
*/
|
||||
public HostConnection(final HostInfo hostInfo, int connectionTimeout) {
|
||||
public HostConnection(final HostInfo hostInfo, int connectTimeout) {
|
||||
this.hostInfo = hostInfo;
|
||||
// Start with the default host protocol
|
||||
this.protocol = hostInfo.getProtocol();
|
||||
// Create a single threaded executor
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
// Set timeout
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,8 +358,8 @@ public class HostConnection {
|
|||
// LogUtils.LOGD(TAG, "Opening HTTP connection.");
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(hostInfo.getJsonRpcHttpEndpoint()).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setConnectTimeout(connectionTimeout);
|
||||
//connection.setReadTimeout(connectionTimeout);
|
||||
connection.setConnectTimeout(connectTimeout);
|
||||
//connection.setReadTimeout(connectTimeout);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
|
||||
|
@ -482,10 +484,6 @@ public class HostConnection {
|
|||
*/
|
||||
private <T> void executeThroughTcp(final ApiMethod<T> method, final ApiCallback<T> callback,
|
||||
final Handler handler) {
|
||||
|
||||
// TODO: We're going to create a background listener thread.
|
||||
// Also create a thread that periodically checks if the connection should be shutdown
|
||||
// based on not having activity. Use android timer or Thread.sleep
|
||||
String methodId = String.valueOf(method.getId());
|
||||
try {
|
||||
// Save this method/callback for later response
|
||||
|
@ -534,13 +532,11 @@ public class HostConnection {
|
|||
|
||||
Socket socket = new Socket();
|
||||
final InetSocketAddress address = new InetSocketAddress(hostInfo.getAddress(), hostInfo.getTcpPort());
|
||||
socket.setSoTimeout(0); // No read timeout. Read should block
|
||||
socket.connect(address, connectionTimeout);
|
||||
// We're setting a read timeout on the socket, so no need to explicitly close it
|
||||
socket.setSoTimeout(TCP_READ_TIMEOUT);
|
||||
socket.connect(address, connectTimeout);
|
||||
|
||||
return socket;
|
||||
} catch (SocketException e) {
|
||||
LogUtils.LOGW(TAG, "Failed to open TCP connection to host: " + hostInfo.getAddress());
|
||||
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_CONNECTING, e);
|
||||
} catch (IOException e) {
|
||||
LogUtils.LOGW(TAG, "Failed to open TCP connection to host: " + hostInfo.getAddress());
|
||||
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_CONNECTING, e);
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 com.syncedsynapse.kore2.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.syncedsynapse.kore2.host.HostManager;
|
||||
import com.syncedsynapse.kore2.jsonrpc.HostConnection;
|
||||
import com.syncedsynapse.kore2.jsonrpc.method.Player;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.GlobalType;
|
||||
import com.syncedsynapse.kore2.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* Service that implements some player actions
|
||||
* Used to support the notifications actions
|
||||
*/
|
||||
public class IntentActionsService extends Service {
|
||||
public static final String TAG = LogUtils.makeLogTag(IntentActionsService.class);
|
||||
|
||||
public static final String EXTRA_PLAYER_ID = "extra_player_id";
|
||||
|
||||
public static final String ACTION_PLAY_PAUSE = "play_pause",
|
||||
ACTION_REWIND = "rewind",
|
||||
ACTION_FAST_FORWARD = "fast_forward",
|
||||
ACTION_PREVIOUS = "previous",
|
||||
ACTION_NEXT = "next";
|
||||
|
||||
@Override
|
||||
public void onCreate() { }
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// We won't create a new thread because the request to the host are
|
||||
// already done in a separate thread. Just fire the request and forget
|
||||
HostConnection hostConnection = HostManager.getInstance(this).getConnection();
|
||||
|
||||
String action = intent.getAction();
|
||||
int playerId = intent.getIntExtra(EXTRA_PLAYER_ID, -1);
|
||||
|
||||
if ((hostConnection != null) && (playerId != -1)) {
|
||||
switch (action) {
|
||||
case ACTION_PLAY_PAUSE:
|
||||
hostConnection.execute(
|
||||
new Player.PlayPause(playerId),
|
||||
null, null);
|
||||
break;
|
||||
case ACTION_REWIND:
|
||||
hostConnection.execute(
|
||||
new Player.SetSpeed(playerId, GlobalType.IncrementDecrement.DECREMENT),
|
||||
null, null);
|
||||
break;
|
||||
case ACTION_FAST_FORWARD:
|
||||
hostConnection.execute(
|
||||
new Player.SetSpeed(playerId, GlobalType.IncrementDecrement.INCREMENT),
|
||||
null, null);
|
||||
break;
|
||||
case ACTION_PREVIOUS:
|
||||
hostConnection.execute(
|
||||
new Player.GoTo(playerId, Player.GoTo.PREVIOUS),
|
||||
null, null);
|
||||
break;
|
||||
case ACTION_NEXT:
|
||||
hostConnection.execute(
|
||||
new Player.GoTo(playerId, Player.GoTo.NEXT),
|
||||
null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) { return null; }
|
||||
}
|
|
@ -605,8 +605,7 @@ public class LibrarySyncService extends Service {
|
|||
@Override
|
||||
public void onSucess(VideoType.DetailsTVShow result) {
|
||||
deleteTVShows(contentResolver, hostId, tvshowId);
|
||||
List<VideoType.DetailsTVShow> tvShows =
|
||||
new ArrayList<VideoType.DetailsTVShow>(1);
|
||||
List<VideoType.DetailsTVShow> tvShows = new ArrayList<>(1);
|
||||
tvShows.add(result);
|
||||
insertTVShowsAndGetDetails(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows);
|
||||
|
@ -908,8 +907,7 @@ public class LibrarySyncService extends Service {
|
|||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
chainCallSyncArtists(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
0, new ArrayList<AudioType.DetailsArtist>());
|
||||
chainCallSyncArtists(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
|
||||
private final static String getArtistsProperties[] = {
|
||||
|
@ -932,8 +930,7 @@ public class LibrarySyncService extends Service {
|
|||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx,
|
||||
final List<AudioType.DetailsArtist> allResults) {
|
||||
final int startIdx) {
|
||||
// Artists->Genres->Albums->Songs
|
||||
// Only gets album artists (first parameter)
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_ARTISTS);
|
||||
|
@ -941,26 +938,27 @@ public class LibrarySyncService extends Service {
|
|||
action.execute(hostConnection, new ApiCallback<List<AudioType.DetailsArtist>>() {
|
||||
@Override
|
||||
public void onSucess(List<AudioType.DetailsArtist> result) {
|
||||
allResults.addAll(result);
|
||||
if (result == null) result = new ArrayList<>(0); // Safeguard
|
||||
// First delete all music info
|
||||
if (startIdx == 0) deleteMusicInfo(contentResolver, hostId);
|
||||
|
||||
// Insert artists
|
||||
ContentValues artistValuesBatch[] = new ContentValues[result.size()];
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
AudioType.DetailsArtist artist = result.get(i);
|
||||
artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist);
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch);
|
||||
|
||||
if (result.size() == LIMIT_SYNC_ARTISTS) {
|
||||
// Max limit returned, there may be some more
|
||||
LogUtils.LOGD(TAG, "chainCallSyncArtists: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncArtists(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_ARTISTS, allResults);
|
||||
startIdx + LIMIT_SYNC_ARTISTS);
|
||||
} else {
|
||||
// Ok, we have all the shows, insert them
|
||||
// Ok, we have all the artists, proceed
|
||||
LogUtils.LOGD(TAG, "chainCallSyncArtists: Got all results, continuing");
|
||||
|
||||
// First delete all music info
|
||||
deleteMusicInfo(contentResolver, hostId);
|
||||
|
||||
ContentValues artistValuesBatch[] = new ContentValues[allResults.size()];
|
||||
for (int i = 0; i < allResults.size(); i++) {
|
||||
AudioType.DetailsArtist artist = allResults.get(i);
|
||||
artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist);
|
||||
}
|
||||
// Insert the artists and continue the syncing
|
||||
contentResolver.bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch);
|
||||
chainCallSyncGenres(orchestrator, hostConnection, callbackHandler, contentResolver);
|
||||
}
|
||||
}
|
||||
|
@ -1008,6 +1006,7 @@ public class LibrarySyncService extends Service {
|
|||
action.execute(hostConnection, new ApiCallback<List<LibraryType.DetailsGenre>>() {
|
||||
@Override
|
||||
public void onSucess(List<LibraryType.DetailsGenre> result) {
|
||||
if (result == null) result = new ArrayList<>(0); // Safeguard
|
||||
ContentValues genresValuesBatch[] = new ContentValues[result.size()];
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
|
@ -1015,11 +1014,9 @@ public class LibrarySyncService extends Service {
|
|||
genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre);
|
||||
}
|
||||
|
||||
// Insert the genres
|
||||
// Insert the genres and proceed to albums
|
||||
contentResolver.bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch);
|
||||
|
||||
chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
0, new ArrayList<AudioType.DetailsAlbum>());
|
||||
chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1052,8 +1049,7 @@ public class LibrarySyncService extends Service {
|
|||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx,
|
||||
final List<AudioType.DetailsAlbum> allResults) {
|
||||
final int startIdx) {
|
||||
final long albumSyncStartTime = System.currentTimeMillis();
|
||||
// Albums->Songs
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_ALBUMS);
|
||||
|
@ -1061,68 +1057,60 @@ public class LibrarySyncService extends Service {
|
|||
action.execute(hostConnection, new ApiCallback<List<AudioType.DetailsAlbum>>() {
|
||||
@Override
|
||||
public void onSucess(List<AudioType.DetailsAlbum> result) {
|
||||
allResults.addAll(result);
|
||||
if (result == null) result = new ArrayList<>(0); // Safeguard
|
||||
// Insert the partial results
|
||||
ContentValues albumValuesBatch[] = new ContentValues[result.size()];
|
||||
int artistsCount = 0, genresCount = 0;
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
AudioType.DetailsAlbum album = result.get(i);
|
||||
albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album);
|
||||
|
||||
artistsCount += album.artistid.size();
|
||||
genresCount += album.genreid.size();
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting albums in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
// Iterate on each album, collect the artists and the genres and insert them
|
||||
ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount];
|
||||
ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount];
|
||||
int artistCount = 0, genreCount = 0;
|
||||
for (AudioType.DetailsAlbum album : result) {
|
||||
for (int artistId : album.artistid) {
|
||||
albumArtistsValuesBatch[artistCount] = new ContentValues();
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId);
|
||||
artistCount++;
|
||||
}
|
||||
|
||||
for (int genreId : album.genreid) {
|
||||
albumGenresValuesBatch[genreCount] = new ContentValues();
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId);
|
||||
genreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch);
|
||||
contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting artists and genres in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
if (result.size() == LIMIT_SYNC_ALBUMS) {
|
||||
// Max limit returned, there may be some more
|
||||
LogUtils.LOGD(TAG, "chainCallSyncAlbums: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_ALBUMS, allResults);
|
||||
startIdx + LIMIT_SYNC_ALBUMS);
|
||||
} else {
|
||||
// Ok, we have all the shows, insert them
|
||||
// Ok, we have all the albums, proceed to songs
|
||||
LogUtils.LOGD(TAG, "chainCallSyncAlbums: Got all results, continuing");
|
||||
|
||||
ContentValues albumValuesBatch[] = new ContentValues[allResults.size()];
|
||||
int artistsCount = 0;
|
||||
int genresCount = 0;
|
||||
for (int i = 0; i < allResults.size(); i++) {
|
||||
AudioType.DetailsAlbum album = allResults.get(i);
|
||||
albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album);
|
||||
|
||||
artistsCount += album.artistid.size();
|
||||
genresCount += album.genreid.size();
|
||||
}
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished parsing albums in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
// Insert the albums
|
||||
contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting albums in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
// Iterate on each album, collect the artists and the genres and insert them
|
||||
ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount];
|
||||
ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount];
|
||||
int artistCount = 0, genreCount = 0;
|
||||
for (AudioType.DetailsAlbum album : allResults) {
|
||||
for (int artistId : album.artistid) {
|
||||
albumArtistsValuesBatch[artistCount] = new ContentValues();
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId);
|
||||
artistCount++;
|
||||
}
|
||||
|
||||
for (int genreId : album.genreid) {
|
||||
albumGenresValuesBatch[genreCount] = new ContentValues();
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId);
|
||||
genreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished parsing artists and genres in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch);
|
||||
contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting artists and genres in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
chainCallSyncSongs(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
0, new ArrayList<AudioType.DetailsSong>());
|
||||
chainCallSyncSongs(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1160,34 +1148,31 @@ public class LibrarySyncService extends Service {
|
|||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx,
|
||||
final List<AudioType.DetailsSong> allResults) {
|
||||
final int startIdx) {
|
||||
// Songs
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_SONGS);
|
||||
AudioLibrary.GetSongs action = new AudioLibrary.GetSongs(limits, getSongsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<List<AudioType.DetailsSong>>() {
|
||||
@Override
|
||||
public void onSucess(List<AudioType.DetailsSong> result) {
|
||||
allResults.addAll(result);
|
||||
if (result == null) result = new ArrayList<>(0); // Safeguard
|
||||
// Save partial results to DB
|
||||
ContentValues songValuesBatch[] = new ContentValues[result.size()];
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
AudioType.DetailsSong song = result.get(i);
|
||||
songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song);
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch);
|
||||
|
||||
if (result.size() == LIMIT_SYNC_SONGS) {
|
||||
// Max limit returned, there may be some more
|
||||
LogUtils.LOGD(TAG, "chainCallSyncSongs: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncSongs(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_SONGS, allResults);
|
||||
startIdx + LIMIT_SYNC_SONGS);
|
||||
} else {
|
||||
// Ok, we have all the songs, insert them
|
||||
LogUtils.LOGD(TAG, "chainCallSyncSongs: Got all results, continuing");
|
||||
|
||||
ContentValues songValuesBatch[] = new ContentValues[allResults.size()];
|
||||
|
||||
for (int i = 0; i < allResults.size(); i++) {
|
||||
AudioType.DetailsSong song = allResults.get(i);
|
||||
songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song);
|
||||
}
|
||||
|
||||
// Insert the songs
|
||||
contentResolver.bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch);
|
||||
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* 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 com.syncedsynapse.kore2.service;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
import com.syncedsynapse.kore2.R;
|
||||
import com.syncedsynapse.kore2.host.HostConnectionObserver;
|
||||
import com.syncedsynapse.kore2.host.HostManager;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.ListType;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.PlayerType;
|
||||
import com.syncedsynapse.kore2.ui.RemoteActivity;
|
||||
import com.syncedsynapse.kore2.utils.CharacterDrawable;
|
||||
import com.syncedsynapse.kore2.utils.LogUtils;
|
||||
import com.syncedsynapse.kore2.utils.UIUtils;
|
||||
import com.syncedsynapse.kore2.utils.Utils;
|
||||
|
||||
/**
|
||||
* This service maintains a notification in the notification area while
|
||||
* something is playing, and keeps running while it is playing.
|
||||
* This service stops itself as soon as the playing stops or there's no
|
||||
* connection. Thus, this should only be started if something is already
|
||||
* playing, otherwise it will shutdown automatically.
|
||||
* It doesn't try to mirror Kodi's state at all times, because that would
|
||||
* imply running at all times which can be resource consuming.
|
||||
*
|
||||
* A {@link HostConnectionObserver} singleton is used to keep track of Kodi's
|
||||
* state. This singleton should be the same as used in the app's activities
|
||||
*/
|
||||
public class NotificationService extends Service
|
||||
implements HostConnectionObserver.PlayerEventsObserver {
|
||||
public static final String TAG = LogUtils.makeLogTag(NotificationService.class);
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
private HostConnectionObserver mHostConnectionObserver = null;
|
||||
|
||||
private PendingIntent mRemoteStartPendingIntent;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// We do not create any thread because all the works is supposed to
|
||||
// be done on the main thread, so that the connection observer
|
||||
// can be shared with the app, and notify it on the UI thread
|
||||
LogUtils.LOGD(TAG, "onCreate");
|
||||
|
||||
// Create the intent to start the remote when the user taps the notification
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
||||
stackBuilder.addParentStack(RemoteActivity.class);
|
||||
stackBuilder.addNextIntent(new Intent(this, RemoteActivity.class));
|
||||
mRemoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.LOGD(TAG, "onStartCommand");
|
||||
// Get the connection observer here, not on create to check if
|
||||
// there has been a change in hosts, and if so unregister the previous one
|
||||
HostConnectionObserver connectionObserver = HostManager.getInstance(this).getHostConnectionObserver();
|
||||
|
||||
// If we are already initialized and the same host, exit
|
||||
if (mHostConnectionObserver == connectionObserver) {
|
||||
LogUtils.LOGD(TAG, "Already initialized");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// If there's a change in hosts, unregister from the previous one
|
||||
if (mHostConnectionObserver != null) {
|
||||
mHostConnectionObserver.unregisterPlayerObserver(this);
|
||||
}
|
||||
|
||||
// Register us on the connection observer
|
||||
mHostConnectionObserver = connectionObserver;
|
||||
mHostConnectionObserver.registerPlayerObserver(this, true);
|
||||
|
||||
// If we get killed, after returning from here, don't restart
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// We don't provide binding, so return null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* HostConnectionObserver.PlayerEventsObserver interface callbacks
|
||||
*/
|
||||
|
||||
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
||||
PlayerType.PropertyValue getPropertiesResult,
|
||||
ListType.ItemsAll getItemResult) {
|
||||
buildNotification(getActivePlayerResult, getPropertiesResult, getItemResult);
|
||||
}
|
||||
|
||||
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
||||
PlayerType.PropertyValue getPropertiesResult,
|
||||
ListType.ItemsAll getItemResult) {
|
||||
buildNotification(getActivePlayerResult, getPropertiesResult, getItemResult);
|
||||
}
|
||||
|
||||
public void playerOnStop() {
|
||||
removeNotification();
|
||||
// Stop service
|
||||
LogUtils.LOGD(TAG, "Shutting down notification service - Player stopped");
|
||||
mHostConnectionObserver.unregisterPlayerObserver(this);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
public void playerNoResultsYet() {
|
||||
removeNotification();
|
||||
}
|
||||
|
||||
public void playerOnConnectionError(int errorCode, String description) {
|
||||
removeNotification();
|
||||
// Stop service
|
||||
LogUtils.LOGD(TAG, "Shutting down notification service - Connection error");
|
||||
mHostConnectionObserver.unregisterPlayerObserver(this);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
public void systemOnQuit() {
|
||||
removeNotification();
|
||||
// Stop service
|
||||
LogUtils.LOGD(TAG, "Shutting down notification service - System quit");
|
||||
mHostConnectionObserver.unregisterPlayerObserver(this);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
// Ignore this
|
||||
public void inputOnInputRequested(String title, String type, String value) { }
|
||||
|
||||
public void observerOnStopObserving() {
|
||||
// Called when the user changes host
|
||||
removeNotification();
|
||||
LogUtils.LOGD(TAG, "Shutting down notification service - System quit");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
// Picasso target that will be used to load images
|
||||
private static Target picassoTarget = null;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private void buildNotification(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
||||
PlayerType.PropertyValue getPropertiesResult,
|
||||
ListType.ItemsAll getItemResult) {
|
||||
final String title, underTitle, poster;
|
||||
int smallIcon, playPauseIcon, rewindIcon, ffIcon;
|
||||
|
||||
boolean isVideo = ((getItemResult.type.equals(ListType.ItemsAll.TYPE_MOVIE)) ||
|
||||
(getItemResult.type.equals(ListType.ItemsAll.TYPE_EPISODE)));
|
||||
|
||||
switch (getItemResult.type) {
|
||||
case ListType.ItemsAll.TYPE_MOVIE:
|
||||
title = getItemResult.title;
|
||||
underTitle = getItemResult.tagline;
|
||||
poster = getItemResult.thumbnail;
|
||||
smallIcon = R.drawable.ic_movie_white_24dp;
|
||||
break;
|
||||
case ListType.ItemsAll.TYPE_EPISODE:
|
||||
title = getItemResult.title;
|
||||
String seasonEpisode = String.format(getString(R.string.season_episode_abbrev),
|
||||
getItemResult.season, getItemResult.episode);
|
||||
underTitle = String.format("%s | %s", getItemResult.showtitle, seasonEpisode);
|
||||
poster = getItemResult.art.poster;
|
||||
smallIcon = R.drawable.ic_tv_white_24dp;
|
||||
break;
|
||||
case ListType.ItemsAll.TYPE_SONG:
|
||||
title = getItemResult.title;
|
||||
underTitle = getItemResult.displayartist + " - " + getItemResult.album;
|
||||
poster = getItemResult.thumbnail;
|
||||
smallIcon = R.drawable.ic_headset_white_24dp;
|
||||
break;
|
||||
case ListType.ItemsAll.TYPE_MUSIC_VIDEO:
|
||||
title = getItemResult.title;
|
||||
underTitle = Utils.listStringConcat(getItemResult.artist, ", ") + " - " + getItemResult.album;
|
||||
poster = getItemResult.thumbnail;
|
||||
smallIcon = R.drawable.ic_headset_white_24dp;
|
||||
break;
|
||||
default:
|
||||
// We don't know what this is, forget it
|
||||
return;
|
||||
}
|
||||
|
||||
switch (getPropertiesResult.speed) {
|
||||
case 1:
|
||||
playPauseIcon = R.drawable.ic_pause_white_24dp;
|
||||
break;
|
||||
default:
|
||||
playPauseIcon = R.drawable.ic_play_arrow_white_24dp;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create the actions, depending on the type of media
|
||||
PendingIntent rewindPendingItent, ffPendingItent, playPausePendingIntent;
|
||||
playPausePendingIntent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_PLAY_PAUSE);
|
||||
if (getItemResult.type.equals(ListType.ItemsAll.TYPE_SONG)) {
|
||||
rewindPendingItent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_PREVIOUS);
|
||||
rewindIcon = R.drawable.ic_skip_previous_white_24dp;
|
||||
ffPendingItent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_NEXT);
|
||||
ffIcon = R.drawable.ic_skip_next_white_24dp;
|
||||
} else {
|
||||
rewindPendingItent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_REWIND);
|
||||
rewindIcon = R.drawable.ic_fast_rewind_white_24dp;
|
||||
ffPendingItent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_FAST_FORWARD);
|
||||
ffIcon = R.drawable.ic_fast_forward_white_24dp;
|
||||
}
|
||||
|
||||
// Setup the collpased and expanded notifications
|
||||
final RemoteViews collapsedRV = new RemoteViews(this.getPackageName(), R.layout.notification_colapsed);
|
||||
collapsedRV.setImageViewResource(R.id.rewind, rewindIcon);
|
||||
collapsedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent);
|
||||
collapsedRV.setImageViewResource(R.id.play, playPauseIcon);
|
||||
collapsedRV.setOnClickPendingIntent(R.id.play, playPausePendingIntent);
|
||||
collapsedRV.setImageViewResource(R.id.fast_forward, ffIcon);
|
||||
collapsedRV.setOnClickPendingIntent(R.id.fast_forward, ffPendingItent);
|
||||
collapsedRV.setTextViewText(R.id.title, title);
|
||||
collapsedRV.setTextViewText(R.id.text2, underTitle);
|
||||
|
||||
final RemoteViews expandedRV = new RemoteViews(this.getPackageName(), R.layout.notification_expanded);
|
||||
expandedRV.setImageViewResource(R.id.rewind, rewindIcon);
|
||||
expandedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent);
|
||||
expandedRV.setImageViewResource(R.id.play, playPauseIcon);
|
||||
expandedRV.setOnClickPendingIntent(R.id.play, playPausePendingIntent);
|
||||
expandedRV.setImageViewResource(R.id.fast_forward, ffIcon);
|
||||
expandedRV.setOnClickPendingIntent(R.id.fast_forward, ffPendingItent);
|
||||
expandedRV.setTextViewText(R.id.title, title);
|
||||
expandedRV.setTextViewText(R.id.text2, underTitle);
|
||||
final int expandedIconResId;
|
||||
if (isVideo) {
|
||||
expandedIconResId = R.id.icon_slim;
|
||||
expandedRV.setViewVisibility(R.id.icon_slim, View.VISIBLE);
|
||||
expandedRV.setViewVisibility(R.id.icon_square, View.GONE);
|
||||
} else {
|
||||
expandedIconResId = R.id.icon_square;
|
||||
expandedRV.setViewVisibility(R.id.icon_slim, View.GONE);
|
||||
expandedRV.setViewVisibility(R.id.icon_square, View.VISIBLE);
|
||||
}
|
||||
|
||||
// Build the notification
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
final Notification notification = builder
|
||||
.setSmallIcon(smallIcon)
|
||||
.setShowWhen(false)
|
||||
.setOngoing(true)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
|
||||
.setContentIntent(mRemoteStartPendingIntent)
|
||||
.setContent(collapsedRV)
|
||||
.build();
|
||||
|
||||
// This is a convoluted way of loading the image and showing the
|
||||
// notification, but it's what works with Picasso and is efficient.
|
||||
// Here's what's going on:
|
||||
//
|
||||
// 1. The image is loaded asynchronously into a Target, and only after
|
||||
// it is loaded is the notification shown. Using targets is a lot more
|
||||
// efficient than letting Picasso load it directly into the
|
||||
// notification imageview, which causes a lot of flickering
|
||||
//
|
||||
// 2. The target needs to be static, because Picasso only keeps a weak
|
||||
// reference to it, so we need to keed a strong reference and reset it
|
||||
// to null when we're done. We also need to check if it is not null in
|
||||
// case a previous request hasn't finished yet.
|
||||
//
|
||||
// 3. We can only show the notification after the bitmap is loaded into
|
||||
// the target, so it is done in the callback
|
||||
//
|
||||
// 4. We specifically resize the image to the same dimensions used in
|
||||
// the remote, so that Picasso reuses it in the remote and here from the cache
|
||||
Resources resources = this.getResources();
|
||||
final int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width);
|
||||
final int posterHeight = isVideo?
|
||||
resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height):
|
||||
posterWidth;
|
||||
if (picassoTarget == null ) {
|
||||
picassoTarget = new Target() {
|
||||
@Override
|
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||
showNotification(bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapFailed(Drawable errorDrawable) {
|
||||
CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(NotificationService.this, title);
|
||||
showNotification(Utils.drawableToBitmap(avatarDrawable, posterWidth, posterHeight));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareLoad(Drawable placeHolderDrawable) { }
|
||||
|
||||
private void showNotification(Bitmap bitmap) {
|
||||
collapsedRV.setImageViewBitmap(R.id.icon, bitmap);
|
||||
if (Utils.isJellybeanOrLater()) {
|
||||
notification.bigContentView = expandedRV;
|
||||
expandedRV.setImageViewBitmap(expandedIconResId, bitmap);
|
||||
}
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
picassoTarget = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Load the image
|
||||
HostManager hostManager = HostManager.getInstance(this);
|
||||
hostManager.getPicasso()
|
||||
.load(hostManager.getHostInfo().getImageUrl(poster))
|
||||
.resize(posterWidth, posterHeight)
|
||||
.into(picassoTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent buildActionPendingIntent(int playerId, String action) {
|
||||
LogUtils.LOGD(TAG, "Build action: " + action);
|
||||
Intent intent = new Intent(this, IntentActionsService.class)
|
||||
.setAction(action)
|
||||
.putExtra(IntentActionsService.EXTRA_PLAYER_ID, playerId);
|
||||
|
||||
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private void removeNotification() {
|
||||
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import com.syncedsynapse.kore2.utils.Utils;
|
|||
* Controls the presentation of Addons information (list, details)
|
||||
* All the information is presented by specific fragments
|
||||
*/
|
||||
public class AddonsActivity extends HostConnectionActivity
|
||||
public class AddonsActivity extends BaseActivity
|
||||
implements AddonListFragment.OnAddonSelectedListener {
|
||||
private static final String TAG = LogUtils.makeLogTag(AddonsActivity.class);
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* 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 com.syncedsynapse.kore2.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.syncedsynapse.kore2.host.HostManager;
|
||||
import com.syncedsynapse.kore2.jsonrpc.HostConnection;
|
||||
|
||||
/**
|
||||
* This activity manages the closing of the {@link HostConnection} singleton provided by
|
||||
* {@link HostManager}.
|
||||
* All activities that plan to use the {@link HostConnection}, or their fragments do,
|
||||
* should inherit from this class to make sure the connection is closed onPause/
|
||||
*/
|
||||
public class HostConnectionActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// Disconnect from the connections used in the fragments
|
||||
HostConnection connection = HostManager.getInstance(this).getConnection();
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import com.syncedsynapse.kore2.utils.Utils;
|
|||
* Controls the presentation of Movies information (list, details)
|
||||
* All the information is presented by specific fragments
|
||||
*/
|
||||
public class MoviesActivity extends HostConnectionActivity
|
||||
public class MoviesActivity extends BaseActivity
|
||||
implements MovieListFragment.OnMovieSelectedListener {
|
||||
private static final String TAG = LogUtils.makeLogTag(MoviesActivity.class);
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ import com.syncedsynapse.kore2.utils.Utils;
|
|||
* Controls the presentation of Music information (list, details)
|
||||
* All the information is presented by specific fragments
|
||||
*/
|
||||
public class MusicActivity extends HostConnectionActivity
|
||||
public class MusicActivity extends BaseActivity
|
||||
implements ArtistListFragment.OnArtistSelectedListener,
|
||||
AlbumListFragment.OnAlbumSelectedListener,
|
||||
AudioGenresListFragment.OnAudioGenreSelectedListener,
|
||||
|
|
|
@ -214,15 +214,12 @@ public class NowPlayingFragment extends Fragment
|
|||
public void onActivityCreated (Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(false);
|
||||
// Get last result from host observer, so that we update the UI accordingly
|
||||
// One of the PlayerEventsObserver callbacks will be called if there's a result available
|
||||
hostConnectionObserver.replyWithLastResult(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -591,6 +588,7 @@ public class NowPlayingFragment extends Fragment
|
|||
|
||||
// Ignore this
|
||||
public void inputOnInputRequested(String title, String type, String value) {}
|
||||
public void observerOnStopObserving() {}
|
||||
|
||||
/**
|
||||
* Sets whats playing information
|
||||
|
|
|
@ -144,15 +144,12 @@ public class PlaylistFragment extends Fragment
|
|||
super.onActivityCreated(savedInstanceState);
|
||||
// We have options
|
||||
setHasOptionsMenu(true);
|
||||
// Get last result from host observer, so that we update the UI accordingly
|
||||
// One of the PlayerEventsObserver callbacks will be called if there's a result available
|
||||
hostConnectionObserver.replyWithLastResult(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -369,6 +366,7 @@ public class PlaylistFragment extends Fragment
|
|||
|
||||
// Ignore this
|
||||
public void inputOnInputRequested(String title, String type, String value) {}
|
||||
public void observerOnStopObserving() {}
|
||||
|
||||
/**
|
||||
* Starts the call chain to display the playlist
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.support.v4.view.ViewPager;
|
|||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
@ -31,6 +32,7 @@ import android.widget.ImageView;
|
|||
import android.widget.Toast;
|
||||
|
||||
import com.syncedsynapse.kore2.R;
|
||||
import com.syncedsynapse.kore2.Settings;
|
||||
import com.syncedsynapse.kore2.host.HostConnectionObserver;
|
||||
import com.syncedsynapse.kore2.host.HostManager;
|
||||
import com.syncedsynapse.kore2.jsonrpc.ApiCallback;
|
||||
|
@ -39,8 +41,10 @@ import com.syncedsynapse.kore2.jsonrpc.method.AudioLibrary;
|
|||
import com.syncedsynapse.kore2.jsonrpc.method.Input;
|
||||
import com.syncedsynapse.kore2.jsonrpc.method.System;
|
||||
import com.syncedsynapse.kore2.jsonrpc.method.VideoLibrary;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.GlobalType;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.ListType;
|
||||
import com.syncedsynapse.kore2.jsonrpc.type.PlayerType;
|
||||
import com.syncedsynapse.kore2.service.NotificationService;
|
||||
import com.syncedsynapse.kore2.ui.hosts.AddHostActivity;
|
||||
import com.syncedsynapse.kore2.ui.views.CirclePageIndicator;
|
||||
import com.syncedsynapse.kore2.utils.LogUtils;
|
||||
|
@ -51,7 +55,7 @@ import butterknife.ButterKnife;
|
|||
import butterknife.InjectView;
|
||||
|
||||
|
||||
public class RemoteActivity extends HostConnectionActivity
|
||||
public class RemoteActivity extends BaseActivity
|
||||
implements HostConnectionObserver.PlayerEventsObserver,
|
||||
NowPlayingFragment.NowPlayingListener,
|
||||
SendTextDialogFragment.SendTextDialogListener {
|
||||
|
@ -127,9 +131,9 @@ public class RemoteActivity extends HostConnectionActivity
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver = hostManager.getHostConnectionObserver();
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
// Get last result
|
||||
hostConnectionObserver.replyWithLastResult(this);
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
// Force a refresh, mainly to update the time elapsed on the fragments
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,6 +143,36 @@ public class RemoteActivity extends HostConnectionActivity
|
|||
hostConnectionObserver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override hardware volume keys and send to Kodi
|
||||
*/
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
// Check whether we should intercept this
|
||||
boolean useVolumeKeys = PreferenceManager
|
||||
.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Settings.KEY_PREF_USE_HARDWARE_VOLUME_KEYS,
|
||||
Settings.DEFAULT_PREF_USE_HARDWARE_VOLUME_KEYS);
|
||||
if (useVolumeKeys) {
|
||||
int action = event.getAction();
|
||||
int keyCode = event.getKeyCode();
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
new Application.SetVolume(GlobalType.IncrementDecrement.INCREMENT).execute(hostManager.getConnection(), null, null);
|
||||
}
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
new Application.SetVolume(GlobalType.IncrementDecrement.DECREMENT).execute(hostManager.getConnection(), null, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (!navigationDrawerFragment.isDrawerOpen()) {
|
||||
|
@ -273,7 +307,7 @@ public class RemoteActivity extends HostConnectionActivity
|
|||
|
||||
/**
|
||||
* Sets or clear the image background
|
||||
* @param url
|
||||
* @param url Image url
|
||||
*/
|
||||
private void setImageViewBackground(String url) {
|
||||
if (url != null) {
|
||||
|
@ -331,6 +365,16 @@ public class RemoteActivity extends HostConnectionActivity
|
|||
setImageViewBackground(imageUrl);
|
||||
}
|
||||
lastImageUrl = imageUrl;
|
||||
|
||||
// Check whether we should show a notification
|
||||
boolean showNotification = PreferenceManager
|
||||
.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION, Settings.DEFAULT_PREF_SHOW_NOTIFICATION);
|
||||
if (showNotification) {
|
||||
// Let's start the notification service
|
||||
LogUtils.LOGD(TAG, "Starting notification service");
|
||||
startService(new Intent(this, NotificationService.class));
|
||||
}
|
||||
}
|
||||
|
||||
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
||||
|
@ -365,6 +409,8 @@ public class RemoteActivity extends HostConnectionActivity
|
|||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
public void observerOnStopObserving() {}
|
||||
|
||||
/**
|
||||
* Now playing fragment listener
|
||||
*/
|
||||
|
|
|
@ -168,15 +168,12 @@ public class RemoteFragment extends Fragment
|
|||
public void onActivityCreated (Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(false);
|
||||
// Get last result from host observer, so that we update the UI accordingly
|
||||
// One of the PlayerEventsObserver callbacks will be called if there's a result available
|
||||
hostConnectionObserver.replyWithLastResult(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -339,6 +336,7 @@ public class RemoteFragment extends Fragment
|
|||
|
||||
// Ignore this
|
||||
public void inputOnInputRequested(String title, String type, String value) {}
|
||||
public void observerOnStopObserving() {}
|
||||
|
||||
/**
|
||||
* Sets whats playing information
|
||||
|
|
|
@ -35,7 +35,7 @@ import com.syncedsynapse.kore2.utils.Utils;
|
|||
* Controls the presentation of TV Shows information (list, details)
|
||||
* All the information is presented by specific fragments
|
||||
*/
|
||||
public class TVShowsActivity extends HostConnectionActivity
|
||||
public class TVShowsActivity extends BaseActivity
|
||||
implements TVShowListFragment.OnTVShowSelectedListener,
|
||||
TVShowEpisodeListFragment.OnEpisodeSelectedListener {
|
||||
private static final String TAG = LogUtils.makeLogTag(TVShowsActivity.class);
|
||||
|
|
|
@ -16,12 +16,10 @@
|
|||
|
||||
package com.syncedsynapse.kore2.utils;
|
||||
|
||||
import com.syncedsynapse.kore2.BuildConfig;
|
||||
import com.syncedsynapse.kore2.host.HostConnectionObserver;
|
||||
import com.syncedsynapse.kore2.jsonrpc.HostConnection;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.syncedsynapse.kore2.BuildConfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -33,8 +31,8 @@ public class LogUtils {
|
|||
|
||||
// TODO: Remove this later
|
||||
private static final List<String> doNotLogTags = Arrays.asList(
|
||||
HostConnection.TAG,
|
||||
HostConnectionObserver.TAG
|
||||
// HostConnection.TAG
|
||||
// HostConnectionObserver.TAG
|
||||
);
|
||||
|
||||
public static String makeLogTag(String str) {
|
||||
|
|
|
@ -129,22 +129,8 @@ public class UIUtils {
|
|||
String imageUrl, String stringAvatar,
|
||||
ImageView imageView,
|
||||
int imageWidth, int imageHeight) {
|
||||
// Load character avatar
|
||||
if (characterAvatarColors == null) {
|
||||
characterAvatarColors = context.getResources()
|
||||
.obtainTypedArray(R.array.character_avatar_colors);
|
||||
}
|
||||
|
||||
char charAvatar = TextUtils.isEmpty(stringAvatar) ?
|
||||
' ' : stringAvatar.charAt(0);
|
||||
avatarColorsIdx = TextUtils.isEmpty(stringAvatar) ? 0 :
|
||||
Math.max(Character.getNumericValue(stringAvatar.charAt(0)) +
|
||||
Character.getNumericValue(stringAvatar.charAt(stringAvatar.length() - 1)) +
|
||||
stringAvatar.length(), 0) % characterAvatarColors.length();
|
||||
int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000);
|
||||
CharacterDrawable avatarDrawable = new CharacterDrawable(charAvatar, color);
|
||||
|
||||
// avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length());
|
||||
CharacterDrawable avatarDrawable = getCharacterAvatar(context, stringAvatar);
|
||||
if (TextUtils.isEmpty(imageUrl)) {
|
||||
imageView.setImageDrawable(avatarDrawable);
|
||||
return;
|
||||
|
@ -166,6 +152,31 @@ public class UIUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CharacterDrawable that is suitable to use as an avatar
|
||||
* @param context Context
|
||||
* @param str String to use to create the avatar
|
||||
* @return Character avatar to use in a image view
|
||||
*/
|
||||
public static CharacterDrawable getCharacterAvatar(Context context, String str) {
|
||||
// Load character avatar
|
||||
if (characterAvatarColors == null) {
|
||||
characterAvatarColors = context
|
||||
.getResources()
|
||||
.obtainTypedArray(R.array.character_avatar_colors);
|
||||
}
|
||||
|
||||
char charAvatar = TextUtils.isEmpty(str) ?
|
||||
' ' : str.charAt(0);
|
||||
avatarColorsIdx = TextUtils.isEmpty(str) ? 0 :
|
||||
Math.max(Character.getNumericValue(str.charAt(0)) +
|
||||
Character.getNumericValue(str.charAt(str.length() - 1)) +
|
||||
str.length(), 0) % characterAvatarColors.length();
|
||||
int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000);
|
||||
// avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length());
|
||||
return new CharacterDrawable(charAvatar, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets play/pause button icon on a ImageView, based on speed
|
||||
* @param context Activity
|
||||
|
|
|
@ -17,6 +17,10 @@ package com.syncedsynapse.kore2.utils;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
@ -131,4 +135,23 @@ public class Utils {
|
|||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a drawable to a bitmap
|
||||
* @param drawable Drawable to convert
|
||||
* @return Bitmap
|
||||
*/
|
||||
public static Bitmap drawableToBitmap (Drawable drawable, int width, int height) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable)drawable).getBitmap();
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/background_floating_material_dark">
|
||||
|
||||
<ImageView android:id="@+id/icon"
|
||||
android:layout_width="@dimen/notification_art_default_width"
|
||||
android:layout_height="@dimen/notification_art_default_height"
|
||||
android:layout_weight="0"
|
||||
android:padding="12dp"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@string/poster"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="fill_vertical"
|
||||
android:minHeight="@dimen/notification_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/small_padding"
|
||||
android:textAppearance="@style/TextAppearance.Notification.Title"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"/>
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Notification.Details"
|
||||
android:singleLine="true"
|
||||
android:fadingEdge="horizontal"
|
||||
android:ellipsize="marquee"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/rewind"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:contentDescription="@string/rewind"/>
|
||||
<ImageButton
|
||||
android:id="@+id/play"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:contentDescription="@string/play"/>
|
||||
<ImageButton
|
||||
android:id="@+id/fast_forward"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:contentDescription="@string/fast_forward"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,224 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--<LinearLayout-->
|
||||
<!--xmlns:android="http://schemas.android.com/apk/res/android"-->
|
||||
<!--android:id="@+id/status_bar_latest_event_content"-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:orientation="horizontal"-->
|
||||
<!--android:background="@color/background_floating_material_dark">-->
|
||||
|
||||
<!--<ImageView android:id="@+id/icon_square"-->
|
||||
<!--android:layout_width="@dimen/notification_expanded_art_default_height"-->
|
||||
<!--android:layout_height="@dimen/notification_expanded_art_default_height"-->
|
||||
<!--android:scaleType="centerCrop"-->
|
||||
<!--android:contentDescription="@string/poster"-->
|
||||
<!--android:visibility="gone"/>-->
|
||||
<!--<ImageView android:id="@+id/icon_slim"-->
|
||||
<!--android:layout_width="@dimen/notification_expanded_art_slim_width"-->
|
||||
<!--android:layout_height="@dimen/notification_expanded_art_default_height"-->
|
||||
<!--android:scaleType="centerCrop"-->
|
||||
<!--android:contentDescription="@string/poster"-->
|
||||
<!--android:visibility="gone"/>-->
|
||||
|
||||
<!--<LinearLayout-->
|
||||
<!--android:layout_width="0dp"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:layout_weight="1"-->
|
||||
<!--android:layout_gravity="fill_vertical"-->
|
||||
<!--android:minHeight="@dimen/notification_expanded_height"-->
|
||||
<!--android:orientation="vertical">-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:id="@+id/title"-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:paddingTop="@dimen/small_padding"-->
|
||||
<!--android:layout_marginLeft="12dp"-->
|
||||
<!--android:layout_marginStart="12dp"-->
|
||||
<!--android:layout_marginRight="12dp"-->
|
||||
<!--android:layout_marginEnd="12dp"-->
|
||||
<!--android:textAppearance="@style/TextAppearance.Notification.Title"-->
|
||||
<!--android:singleLine="true"-->
|
||||
<!--android:ellipsize="marquee"-->
|
||||
<!--android:fadingEdge="horizontal"/>-->
|
||||
<!--<TextView-->
|
||||
<!--android:id="@+id/text2"-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="0dp"-->
|
||||
<!--android:layout_weight="1"-->
|
||||
<!--android:layout_marginLeft="12dp"-->
|
||||
<!--android:layout_marginStart="12dp"-->
|
||||
<!--android:layout_marginRight="12dp"-->
|
||||
<!--android:layout_marginEnd="12dp"-->
|
||||
<!--android:textAppearance="@style/TextAppearance.Notification.Details"-->
|
||||
<!--android:maxLines="2"-->
|
||||
<!--android:fadingEdge="horizontal"-->
|
||||
<!--android:ellipsize="marquee"/>-->
|
||||
|
||||
<!--<ImageView-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="1dp"-->
|
||||
<!--android:id="@+id/action_divider"-->
|
||||
<!--android:background="#29ffffff"/>-->
|
||||
|
||||
<!--<LinearLayout-->
|
||||
<!--android:id="@+id/media_actions"-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="@dimen/default_icon_size"-->
|
||||
<!--android:layout_marginStart="12dp"-->
|
||||
<!--android:layout_marginEnd="12dp"-->
|
||||
<!--android:orientation="horizontal"-->
|
||||
<!--android:layoutDirection="ltr">-->
|
||||
|
||||
<!--<ImageButton-->
|
||||
<!--android:id="@+id/rewind"-->
|
||||
<!--style="@style/Widget.Button.Borderless"-->
|
||||
<!--android:layout_width="@dimen/default_icon_size"-->
|
||||
<!--android:layout_height="match_parent"-->
|
||||
<!--android:layout_marginLeft="2dp"-->
|
||||
<!--android:layout_marginRight="2dp"-->
|
||||
<!--android:layout_weight="1"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:contentDescription="@string/rewind"/>-->
|
||||
<!--<ImageButton-->
|
||||
<!--android:id="@+id/play"-->
|
||||
<!--style="@style/Widget.Button.Borderless"-->
|
||||
<!--android:layout_width="@dimen/default_icon_size"-->
|
||||
<!--android:layout_height="match_parent"-->
|
||||
<!--android:layout_marginLeft="2dp"-->
|
||||
<!--android:layout_marginRight="2dp"-->
|
||||
<!--android:layout_weight="1"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:contentDescription="@string/play"/>-->
|
||||
<!--<ImageButton-->
|
||||
<!--android:id="@+id/fast_forward"-->
|
||||
<!--style="@style/Widget.Button.Borderless"-->
|
||||
<!--android:layout_width="@dimen/default_icon_size"-->
|
||||
<!--android:layout_height="match_parent"-->
|
||||
<!--android:layout_marginLeft="2dp"-->
|
||||
<!--android:layout_marginRight="2dp"-->
|
||||
<!--android:layout_weight="1"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:contentDescription="@string/fast_forward"/>-->
|
||||
<!--</LinearLayout>-->
|
||||
<!--</LinearLayout>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:background="@color/background_floating_material_dark">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView android:id="@+id/icon_square"
|
||||
android:layout_width="@dimen/notification_expanded_art_default_height"
|
||||
android:layout_height="@dimen/notification_expanded_art_default_height"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@string/poster"
|
||||
android:visibility="gone"/>
|
||||
<ImageView android:id="@+id/icon_slim"
|
||||
android:layout_width="@dimen/notification_expanded_art_slim_width"
|
||||
android:layout_height="@dimen/notification_expanded_art_default_height"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@string/poster"
|
||||
android:visibility="gone"/>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/small_padding"
|
||||
android:textAppearance="@style/TextAppearance.Notification.Title"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"/>
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Notification.Details"
|
||||
android:maxLines="2"
|
||||
android:fadingEdge="horizontal"
|
||||
android:ellipsize="marquee"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/default_icon_size"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/rewind"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"/>
|
||||
<ImageButton
|
||||
android:id="@+id/play"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"/>
|
||||
<ImageButton
|
||||
android:id="@+id/fast_forward"
|
||||
style="@style/Widget.Button.Borderless"
|
||||
android:layout_width="@dimen/default_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_above="@id/media_actions"
|
||||
android:id="@+id/action_divider"
|
||||
android:background="#29ffffff"/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,296 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Kore</string>
|
||||
<string name="settings">Parametri</string>
|
||||
<string name="action_options">Opzioni</string>
|
||||
|
||||
<string name="loading">Caricamento…</string>
|
||||
|
||||
<!-- Navigation drawer tips -->
|
||||
<string name="navigation_drawer_open">Apri il riquadro di navigazione</string>
|
||||
<string name="navigation_drawer_close">Chiudi il riquadro di navigazione</string>
|
||||
|
||||
<string name="xbmc_media_center">Media Center</string>
|
||||
<string name="home">Home</string>
|
||||
<string name="movies">Film</string>
|
||||
<string name="tv_shows">Programmi TV</string>
|
||||
<string name="music">Musica</string>
|
||||
<string name="pictures">Immagini</string>
|
||||
<string name="addons">Estensioni</string>
|
||||
|
||||
<string name="no_xbmc_configured">Media center non configurato</string>
|
||||
<string name="add_xbmc">Aggiungi Media Center</string>
|
||||
|
||||
<string name="xbmc_quit">Media center in chiusura.</string>
|
||||
<string name="wol_sent">Wake up inviato al media center.</string>
|
||||
|
||||
<string name="power">Alimentazione</string>
|
||||
<string name="quit">Esci</string>
|
||||
<string name="suspend">Sospensione</string>
|
||||
<string name="shutdown">Spegni</string>
|
||||
|
||||
<string name="send">Invia</string>
|
||||
<string name="send_text">Invia testo al media center</string>
|
||||
<string name="text_to_send">Testo da inviare</string>
|
||||
<string name="finish_after_send">Chiudi dopo l\'invio</string>
|
||||
|
||||
<string name="library_actions">Gestione libreria</string>
|
||||
<string name="clean_video_library">Pulisci libreria video</string>
|
||||
<string name="clean_audio_library">Pulisci libreria audio</string>
|
||||
<string name="update_video_library">Aggiorna libreria video</string>
|
||||
<string name="update_audio_library">Aggiorna libreria audio</string>
|
||||
|
||||
<string name="toggle_fullscreen">Passa a schermo intero</string>
|
||||
|
||||
<string name="connected_to">Connesso a %1$s</string>
|
||||
<string name="connecting">Connessione…</string>
|
||||
<string name="connecting_to">Connessione a %1$s (%2$s)…</string>
|
||||
|
||||
<!-- String used in add host wizard -->
|
||||
<string name="wizard_welcome">Benvenuto</string>
|
||||
<string name="wizard_welcome_message"><![CDATA[
|
||||
Cominciamo aggiungendo un media center. Assicurati che il tuo Kodi/XBMC sia in esecuzione, correttamente configurato e sulla stessa rete del tuo dispositivo.<br/><br/>
|
||||
Puoi trovare aiuto per la configurazione <a href="http://syncedsynapse.com/kore/kore-faq/>qui</a>.<br/><br/>
|
||||
Quando sei pronto premi su <b><i>Avanti</i></b>.
|
||||
]]></string>
|
||||
<string name="wizard_search_message"><![CDATA[
|
||||
Ricerca dei media center sulla tua rete locale…<br/>
|
||||
]]></string>
|
||||
<string name="wizard_search_no_host_found"><![CDATA[
|
||||
Non riesco a trovare media center sulla tua rete.<br/>Se hai bisogno di aiuto per la configurazione, controlla <a href="http://syncedsynapse.com/kore/kore-faq/>qui</a>.<br/><br/>
|
||||
Clicca su <i>Ricerca</i> per cercare ancora o <i>Avanti</i> per la configurazione manuale.
|
||||
]]></string>
|
||||
<string name="wizard_search_host_found"><![CDATA[
|
||||
Ho trovato questi media centers sulla tua rete.<br/><br/>Selezionane uno da aggiungere o premi <i>Avanti</i> per aggiungerne uno manualmente.
|
||||
]]></string>
|
||||
<string name="searching">Ricerca…</string>
|
||||
<string name="no_xbmc_found">Nessun media center trovato</string>
|
||||
<string name="xbmc_found">Media center trovato</string>
|
||||
<string name="wizard_manual_configuration">Configurazione manuale</string>
|
||||
<string name="wizard_manual_configuration_message">Inserisci la configurazione del tuo media center:</string>
|
||||
<string name="wizard_manual_configuration_message_advanced">Configurazione avanzata (lascia vuoto per la configurazione predefinita)</string>
|
||||
<string name="wizard_xbmc_name">Nome del Media center</string>
|
||||
<string name="wizard_xbmc_ip">Indirizzo</string>
|
||||
<string name="wizard_xbmc_port">Porta</string>
|
||||
<string name="wizard_xbmc_username">Nome utente</string>
|
||||
<string name="wizard_xbmc_password">Password</string>
|
||||
<string name="wizard_xbmc_tcp_port">Porta TCP (9090)</string>
|
||||
<string name="wizard_xbmc_mac_address">Indirizzo MAC</string>
|
||||
<string name="wizard_xbmc_wol_port">Porta WoL (9)</string>
|
||||
|
||||
<string name="wizard_no_name_specified">Indica un nome per questo media center, così potrai identificarlo in seguito.</string>
|
||||
<string name="wizard_no_address_specified">Specifica l\'indirizzo di questo media center, così posso trovarlo.</string>
|
||||
<string name="wizard_invalid_http_port_specified">Specifica una porta HTTP valida per questo media center, così posso trovarlo.</string>
|
||||
<string name="wizard_invalid_tcp_port_specified">Specifica una porta TCP valida per questo media center, così posso trovarlo.</string>
|
||||
|
||||
<string name="wizard_connecting_to_xbmc_title">Connessione a %1$s…</string>
|
||||
<string name="wizard_connecting_to_xbmc_message">Per favore aspetta mentre provo a collegarmi al tuo media center…</string>
|
||||
|
||||
<string name="wizard_empty_authentication">Kodi/XBMC richiede un\'autenticazione.\nPer favore specifica un nome utente e una password.</string>
|
||||
<string name="wizard_incorrect_authentication">Nome utente e/o password non validi.\nPer favore controlla le tue credenziali.</string>
|
||||
<string name="wizard_success_connecting">Connesso a Kodi/XBMC.</string>
|
||||
<string name="wizard_error_connecting">Non posso collegarmi a Kodi/XBMC.\nPer favore verifica la configurazione.</string>
|
||||
|
||||
<string name="wizard_done">Tutto fatto!</string>
|
||||
<string name="wizard_done_message"><![CDATA[
|
||||
Il tuo media center è configurato.<br/>
|
||||
Ora potrai usare il telecomando per controllarlo. La libreria si sta sincronizzando e dovrebbe essere disponibile fra poco.<br/><br/>
|
||||
Premi <b><i>Finito</i></b> per cominciare a usare il telecomando.
|
||||
]]></string>
|
||||
|
||||
<string name="play">Play</string>
|
||||
<string name="pause">Pausa</string>
|
||||
<string name="stop">Arresta</string>
|
||||
<string name="fast_forward">Avanti Veloce</string>
|
||||
<string name="rewind">Indietro</string>
|
||||
|
||||
<string name="repeat">Ripeti</string>
|
||||
<string name="shuffle">Mescola</string>
|
||||
<string name="volume_up">Aumenta Volume</string>
|
||||
<string name="volume_down">Riduci Volume</string>
|
||||
<string name="volume_mute">Muto</string>
|
||||
<string name="subtitles">Sottotitoli</string>
|
||||
<string name="audiostreams">Audio</string>
|
||||
<string name="no_audiostream">Nessun audio disponibile</string>
|
||||
<string name="download_subtitle">Scarica sottotitoli</string>
|
||||
<string name="none">Nessuno</string>
|
||||
|
||||
<string name="left">Sinistra</string>
|
||||
<string name="right">Destra</string>
|
||||
<string name="up">Su</string>
|
||||
<string name="down">Giù</string>
|
||||
<string name="select">Select</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="codec_info">Codec</string>
|
||||
<string name="osd">Menu</string>
|
||||
<string name="back">Indietro</string>
|
||||
|
||||
<string name="previous">Precedente</string>
|
||||
<string name="next">Prossimo</string>
|
||||
<string name="finish">Finito</string>
|
||||
<string name="test_connection">Test</string>
|
||||
<string name="search_again">Cerca ancora</string>
|
||||
|
||||
<string name="remove">Rimuovi</string>
|
||||
<string name="edit">Modifica</string>
|
||||
<string name="wake_up">Wake up</string>
|
||||
<string name="edit_xbmc">Modifica media center</string>
|
||||
<string name="delete_xbmc">Cancella media center</string>
|
||||
<string name="delete_xbmc_confirm">Sei sicuro di voler cancellare questo media center?</string>
|
||||
|
||||
<string name="connecting_to_xbmc">Connessione…</string>
|
||||
<string name="unable_to_connect_to_xbmc">Impossibile connettersi al media center</string>
|
||||
<string name="connected_to_xbmc">Connesso</string>
|
||||
<string name="xbmc_available">Disponibile</string>
|
||||
<string name="xbmc_unavailable">Non disponibile</string>
|
||||
|
||||
<string name="nothing_playing">Niente in play</string>
|
||||
|
||||
<!-- Main view tabs -->
|
||||
<string name="now_playing">Ora in play</string>
|
||||
<string name="remote">Telecomando</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
|
||||
<string name="season_episode_abbrev">s%1$02de%2$02d</string>
|
||||
<string name="season_episode">Stagione %1$02d | Episodio %2$02d</string>
|
||||
<string name="season_number">Stagione %1$02d</string>
|
||||
<string name="episode_number">%1$d</string>
|
||||
|
||||
<string name="votes">%1$s voti</string>
|
||||
<string name="max_rating_video">/10</string>
|
||||
<string name="max_rating_music">/5</string>
|
||||
|
||||
<string name="fanart">Fanart</string>
|
||||
<string name="poster">Poster</string>
|
||||
<string name="thumbnail">Thumbnail</string>
|
||||
|
||||
<string name="error_getting_properties">Non posso leggere i parametri di Kodi/XBMC.\nMessaggio di errore:
|
||||
%1$s.</string>
|
||||
<string name="error_executing_subtitles">Non posso eseguire l\'estensione per i sottotitoli.\nMessaggio di errore: %1$s.</string>
|
||||
<string name="error_getting_addon_info">Non posso acquisire informazioni sulle estensioni.\nMessaggio di errore:
|
||||
%1$s.</string>
|
||||
|
||||
<string name="directors">Registi:</string>
|
||||
<string name="studio">Studio:</string>
|
||||
<string name="cast">Cast</string>
|
||||
<string name="additional_cast">Cast secondario</string>
|
||||
<string name="cast_list_text">%1$s as %2$s</string>
|
||||
|
||||
<string name="general_error_executing_action">Errore durante l\'operazione: %1$s</string>
|
||||
<string name="error_getting_playlist">Errore nella lettura della playlist</string>
|
||||
<string name="error_message">Messaggio di errore: %1$s</string>
|
||||
<string name="playlist_empty">Playlist vuota</string>
|
||||
<string name="clear_playlist">Pulisci playlist</string>
|
||||
|
||||
<string name="no_movies_found_refresh">Nessun film trovato\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="no_tvshows_found_refresh">Nessun programma TV trovato\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="no_episodes_found">Nessun episodio trovato</string>
|
||||
<string name="no_artists_found_refresh">Nessun artista trovato\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="no_albums_found_refresh">Nessim album trovato\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="no_genres_found_refresh">Nessun genere trovato\n\nSwipe down to refresh</string>
|
||||
<string name="no_addons_found_refresh">Nessuna estensione trovata o non connesso\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="no_music_videos_found_refresh">Nessun video trovato\n\nSwipe in giù per aggiornare</string>
|
||||
<string name="pull_to_refresh">Premi per aggiornare</string>
|
||||
|
||||
<string name="minutes_abbrev">%1$s min</string>
|
||||
<string name="sync_successful">Sincronizzazione avvenuto con successo</string>
|
||||
<string name="error_while_syncing">Si è verificato un errore durante la sincronizzazione: %1$s</string>
|
||||
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="action_search_movies">Cerca Film</string>
|
||||
<string name="action_search_tvshows">Cerca spettacoli TV</string>
|
||||
<string name="action_search_albums">Cerca album</string>
|
||||
<string name="action_search_artists">Cerca per artista</string>
|
||||
<string name="action_search_genres">Cerca per genere</string>
|
||||
<string name="action_search_music_videos">Cerca video</string>
|
||||
|
||||
<string name="add_to_playlist">Aggiungi alla playlist</string>
|
||||
<string name="item_added_to_playlist">Aggiunto alla playlist</string>
|
||||
<string name="no_suitable_playlist">Nessuna playlist disponibile per aggiungere il media.</string>
|
||||
|
||||
<string name="imdb">IMDb</string>
|
||||
<string name="seen">Visto</string>
|
||||
<string name="download">Scaricato</string>
|
||||
|
||||
<string name="download_file_exists">File già esistente.\nVuoi sovrascriverlo o scaricarlo con un nuovo nome?</string>
|
||||
<string name="download_dir_exists">Cartella download esistente.\nSe esistono file con lo stesso nome, vuoi sovrascriverli o scaricarli con un nuovo nome?</string>
|
||||
<string name="overwrite">Sovrascrivi</string>
|
||||
<string name="download_with_new_name">Nuovo nome</string>
|
||||
<string name="download_file_description">Scaricato dal tuo media center</string>
|
||||
|
||||
<string name="num_episodes">%1$d episodi | %2$d non visti</string>
|
||||
<string name="premiered">Premiere: %1$s</string>
|
||||
|
||||
<string name="tvshow_overview">Riepilogo</string>
|
||||
<string name="tvshow_episodes">Episodi</string>
|
||||
|
||||
<string name="artists">Artisti</string>
|
||||
<string name="albums">Album</string>
|
||||
<string name="genres">Generi</string>
|
||||
<string name="music_videos">Video</string>
|
||||
|
||||
<string name="no_files_to_download">Nessun file da scaricare.</string>
|
||||
<string name="error_getting_file_information">Non posso ricevere informazioni per scaricare il file %1$s.</string>
|
||||
|
||||
<string name="author">Autore:</string>
|
||||
<string name="version">Versione:</string>
|
||||
<string name="enable_disable">Abilita/Disabilita Estensioni</string>
|
||||
<string name="addon_enabled">Estensioni abilitate</string>
|
||||
<string name="addon_disabled">Estensioni disabilitate</string>
|
||||
|
||||
<!-- Filters on list menus -->
|
||||
<string name="hide_watched">Nascondi già visti</string>
|
||||
<string name="sort_order">Ordina</string>
|
||||
<string name="sort_by_name">Per nome</string>
|
||||
<string name="sort_by_date_added">Per data di inserimento</string>
|
||||
<string name="ignore_prefixes">Ignora prefissi</string>
|
||||
|
||||
<!-- Preferences strings -->
|
||||
<string name="theme">Tema</string>
|
||||
<string name="theme_night">Notte</string>
|
||||
<string name="theme_day">Giorno</string>
|
||||
<string name="theme_mist">Nebbia</string>
|
||||
<string name="theme_solarized">Solarized</string>
|
||||
<string name="theme_solarized_dark">Solarized Dark</string>
|
||||
|
||||
<string name="switch_to_remote">Switch to remote after media start</string>
|
||||
|
||||
|
||||
<string name="about">About</string>
|
||||
<string name="about_desc"><![CDATA[
|
||||
\u00A9 2015 Synced Synapse.<br><br>
|
||||
Per favore votaci su <b><a href="market://details?id=com.syncedsynapse.kore2">Google Play</a></b><br><br>
|
||||
|
||||
Se ti serve aiuto, controlla le <b><a href="http://syncedsynapse.com/kore/kore-faq/">FAQ</a></b> o
|
||||
segui Kore <b><a href="https://plus.google.com/u/0/communities/110340113064213296333">Google+ Community</a></b>
|
||||
]]></string>
|
||||
|
||||
<!-- String for coffee -->
|
||||
<string name="buy_me_coffee">Offrimi un caffè</string>
|
||||
<string name="expresso_please">Un espresso, per favore. Grazie!</string>
|
||||
<string name="thanks_for_coffe">Grazie per il caffè!</string>
|
||||
<string name="remove_coffee_message">Premi per nascondere questa impostazione</string>
|
||||
<string name="buy_coffee_to_unlock_themes">Per favore offrimi un caffè per sbloccare più temi</string>
|
||||
|
||||
<string name="error_setting_up_billing">Non risco ad accedere al Google Play Billing Service:\n%s</string>
|
||||
<string name="error_querying_inventory">Errore durante l\'interrogazione dell\'inventario.</string>
|
||||
<string name="error_during_purchased">Si è un errore durante l\'acquisto.</string>
|
||||
<string name="purchase_thanks">Grazie per il tuo supporto!</string>
|
||||
|
||||
</resources>
|
|
@ -114,4 +114,16 @@
|
|||
|
||||
<dimen name="addondetail_poster_width">112dp</dimen>
|
||||
<dimen name="addondetail_poster_heigth">112dp</dimen>
|
||||
|
||||
<!-- Notification -->
|
||||
<dimen name="notification_height">64dp</dimen>
|
||||
<dimen name="notification_expanded_height">128dp</dimen>
|
||||
<dimen name="notification_art_default_height">64dp</dimen>
|
||||
<dimen name="notification_art_default_width">64dp</dimen>
|
||||
<dimen name="notification_art_slim_width">42dp</dimen>
|
||||
<dimen name="notification_expanded_art_default_height">128dp</dimen>
|
||||
<dimen name="notification_expanded_art_default_width">128dp</dimen>
|
||||
<dimen name="notification_expanded_art_slim_width">84dp</dimen>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -270,7 +270,8 @@
|
|||
<string name="theme_solarized_dark">Solarized Dark</string>
|
||||
|
||||
<string name="switch_to_remote">Switch to remote after media start</string>
|
||||
|
||||
<string name="show_notification">Show notification while playing</string>
|
||||
<string name="use_hardware_volume_keys">Use volume keys to control volume</string>
|
||||
|
||||
<string name="about">About</string>
|
||||
<string name="about_desc"><![CDATA[
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<item name="android:paddingBottom">@dimen/large_padding</item>
|
||||
<item name="android:paddingLeft">@dimen/large_padding</item>
|
||||
<item name="android:paddingRight">@dimen/large_padding</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
<item name="android:maxLines">2</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
|
||||
|
@ -282,5 +282,20 @@
|
|||
<!--<item name="android:maxLines">2</item>-->
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Notification">
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Notification.Title">
|
||||
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Title</item>
|
||||
<item name="android:textColor">@color/primary_text_default_material_dark</item>
|
||||
<item name="android:textSize">@dimen/text_size_large</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Notification.Details">
|
||||
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Body1</item>
|
||||
<item name="android:textColor">@color/secondary_text_default_material_dark</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
|
|
|
@ -34,6 +34,16 @@
|
|||
android:title="@string/switch_to_remote"
|
||||
android:defaultValue="true"/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="pref_show_notification"
|
||||
android:title="@string/show_notification"
|
||||
android:defaultValue="false"/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="pref_use_hardware_volume_keys"
|
||||
android:title="@string/use_hardware_volume_keys"
|
||||
android:defaultValue="true"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_about"
|
||||
android:title="@string/about"/>
|
||||
|
|
Loading…
Reference in New Issue