/* * Copyright (C) 2016 Jorge Ruesga * * 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.ruesga.android.wallpapers.photophase.cast; import android.animation.Animator; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback; import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.ruesga.android.wallpapers.photophase.AndroidHelper; import com.ruesga.android.wallpapers.photophase.ICastService; import com.ruesga.android.wallpapers.photophase.R; import com.ruesga.android.wallpapers.photophase.preferences.PreferencesProvider; import com.ruesga.android.wallpapers.photophase.tasks.AsyncPictureLoaderTask; import com.ruesga.android.wallpapers.photophase.tasks.AsyncPictureLoaderTask.AsyncPictureLoaderRunnable; import com.ruesga.android.wallpapers.photophase.utils.BitmapUtils; import com.ruesga.android.wallpapers.photophase.widgets.PlayPauseDrawable; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class CastPhotoQueueActivity extends AppCompatActivity implements OnClickListener { private static final String TAG = "CastPhotoQueueActivity"; private class QueueViewHolder extends RecyclerView.ViewHolder { private ImageView mPhoto; public QueueViewHolder(View itemView) { super(itemView); mPhoto = (ImageView) itemView.findViewById(R.id.queue_photo); } } private class QueueAdapter extends RecyclerView.Adapter<QueueViewHolder> { private final List<String> mQueue; private final Context mContext; private final LayoutInflater mLayoutInflater; private final int mPhotoSize; private final OnClickListener mClickListener; private String mCurrent; public QueueAdapter(Context ctx, List<String> queue, OnClickListener clickListener) { mQueue = queue; mContext = ctx; mLayoutInflater = LayoutInflater.from(ctx); mPhotoSize = (int) ctx.getResources().getDimension(R.dimen.queue_photo_size); mClickListener = clickListener; mCurrent = null; } @Override public QueueViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = mLayoutInflater.inflate(R.layout.cast_queue_item, parent, false); return new QueueViewHolder(v); } @Override public void onBindViewHolder(final QueueViewHolder holder, int position) { synchronized (mQueue) { // Cancel any non finished task if (holder.mPhoto.getTag() != null) { AsyncPictureLoaderRunnable task = (AsyncPictureLoaderRunnable) holder.mPhoto.getTag(); if (task != null) { holder.mPhoto.removeCallbacks(task); if (task.mTask.getStatus() != AsyncTask.Status.FINISHED) { task.mTask.cancel(true); } } } // Update the item final String item = mQueue.get(position); final boolean selected = mCurrent != null && mCurrent.equals(item); holder.itemView.setTag(position); holder.itemView.setOnClickListener(mClickListener); if (mCache.containsKey(item)) { holder.itemView.setSelected(selected); holder.mPhoto.setImageBitmap(mCache.get(item)); holder.mPhoto.setTag(null); } else { holder.mPhoto.setImageBitmap(null); holder.itemView.setSelected(false); File f = new File(item); AsyncPictureLoaderTask task = new AsyncPictureLoaderTask(mContext, holder.mPhoto, mPhotoSize, mPhotoSize, new AsyncPictureLoaderTask.OnPictureLoaded() { @Override public void onPictureLoaded(Object o, Drawable drawable) { holder.itemView.setSelected(selected); if (drawable instanceof BitmapDrawable) { mCache.put(item, ((BitmapDrawable) drawable).getBitmap()); } } }); task.mFactor = 1; AsyncPictureLoaderRunnable runnable = new AsyncPictureLoaderRunnable(task, f); ViewCompat.postOnAnimation(holder.mPhoto, runnable); holder.mPhoto.setTag(runnable); } } } @Override public int getItemCount() { return mQueue.size(); } public void updateCurrent(String current) { mCurrent = current; } } private final ServiceConnection mCastConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { mCastService = ICastService.Stub.asInterface(binder); refreshQueue(true); updateTrackInfo(); } @Override public void onServiceDisconnected(ComponentName componentName) { mCastService = null; finish(); } }; private BroadcastReceiver mCastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mCastService != null) { String action = intent.getAction(); switch (action) { case CastService.ACTION_MEDIA_CHANGED: updateTrackInfo(); break; case CastService.ACTION_QUEUE_CHANGED: refreshQueue(true); break; case CastService.ACTION_LOADING_MEDIA: showLoading(); break; case CastService.ACTION_SERVER_STOP: if (!mPlayPauseDrawable.isPlay()) { mPlayPauseDrawable.getPausePlayAnimator().start(); } updateCurrentPlaying(null); break; case CastService.ACTION_SERVER_EXITED: finish(); break; } } } }; private final OnClickListener mOnItemClickListener = new OnClickListener() { @Override public void onClick(View view) { int position = (Integer) view.getTag(); String media = mQueueList.get(position); if (!mLoadingStatus) { try { mCastService.show(media); mLoadingStatus = true; } catch (RemoteException ex) { Log.w(TAG, "Operation failed (previous)", ex); mLoadingStatus = false; } } } }; private final SimpleCallback mTouchHelperCallback = new SimpleCallback(0, ItemTouchHelper.UP) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int position = viewHolder.getAdapterPosition(); try { String item = mQueueList.get(position); mCastService.remove(item); mCache.remove(item); refreshQueue(false); mQueueAdapter.notifyItemRemoved(position); try { int count = mQueueList.size(); for (int i = position; i < count; i++){ mQueueAdapter.notifyItemChanged(position); } } catch (Exception ex) { // Ignore } } catch (RemoteException ex) { // Ignore } } }; private View mQueueMediaPanel; private ImageView mShuffle; private ImageView mRepeat; private PlayPauseDrawable mPlayPauseDrawable; private View mLogo; private ImageView mPhoto; private TextView mTitle; private TextView mAlbum; private ProgressBar mLoading; private RecyclerView mQueue; private QueueAdapter mQueueAdapter; private final List<String> mQueueList = new ArrayList<>(); private int mOverallXScroll; private ICastService mCastService; private Point mScreenDim; private boolean mLoadingStatus; private Map<String, Bitmap> mCache = new HashMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.cast_queue_activity); if (!PreferencesProvider.Preferences.Cast.isEnabled(this)) { finish(); } // Require hardware acceleration and retain screen on getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); getWindow().setFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Display display = getWindowManager().getDefaultDisplay(); mScreenDim = new Point(); display.getSize(mScreenDim); AndroidHelper.setupRecentBar(this); mQueueMediaPanel = findViewById(R.id.queue_media_panel); mShuffle = (ImageView) findViewById(R.id.shuffle); mRepeat = (ImageView) findViewById(R.id.repeat); ImageView playPause = (ImageView) findViewById(R.id.play_pause); mPlayPauseDrawable = new PlayPauseDrawable(); playPause.setImageDrawable(mPlayPauseDrawable); ImageView previous = (ImageView) findViewById(R.id.previous); ImageView next = (ImageView) findViewById(R.id.next); mLogo = findViewById(R.id.logo); mPhoto = (ImageView) findViewById(R.id.photo); mTitle = (TextView) findViewById(R.id.photo_title); mAlbum = (TextView) findViewById(R.id.photo_album); mLoading = (ProgressBar) findViewById(R.id.loading); mLoadingStatus = false; mQueue = (RecyclerView) findViewById(R.id.queue); mQueue.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); mQueueAdapter = new QueueAdapter(this, mQueueList, mOnItemClickListener); mQueue.setAdapter(mQueueAdapter); mQueue.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { mOverallXScroll += dx; } }); ItemTouchHelper touchHelper = new ItemTouchHelper(mTouchHelperCallback); touchHelper.attachToRecyclerView(mQueue); mShuffle.setOnClickListener(this); mRepeat.setOnClickListener(this); playPause.setOnClickListener(this); previous.setOnClickListener(this); next.setOnClickListener(this); IntentFilter filter = new IntentFilter(); filter.addAction(CastService.ACTION_MEDIA_CHANGED); filter.addAction(CastService.ACTION_QUEUE_CHANGED); filter.addAction(CastService.ACTION_LOADING_MEDIA); filter.addAction(CastService.ACTION_SERVER_STOP); filter.addAction(CastService.ACTION_SERVER_EXITED); LocalBroadcastManager.getInstance(this).registerReceiver(mCastReceiver, filter); try { Intent i = new Intent(this, CastService.class); bindService(i, mCastConnection, Context.BIND_AUTO_CREATE); } catch (SecurityException se) { Log.w(TAG, "Can't bound to CastService", se); } } @Override protected void onDestroy() { super.onDestroy(); if (mCastService != null) { unbindService(mCastConnection); } LocalBroadcastManager.getInstance(this).unregisterReceiver(mCastReceiver); } @Override protected void onResume() { super.onResume(); mRepeat.setSelected(PreferencesProvider.Preferences.Cast.isSlideshowRepeat(this)); mShuffle.setSelected(PreferencesProvider.Preferences.Cast.isSlideshowShuffle(this)); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.shuffle: boolean selected = !view.isSelected(); view.setSelected(selected); PreferencesProvider.Preferences.Cast.setSlideshowShuffle(this, selected); sendConfigurationChangedEvent("cast_slideshow_shuffle"); break; case R.id.repeat: selected = !view.isSelected(); view.setSelected(selected); PreferencesProvider.Preferences.Cast.setSlideshowRepeat(this, selected); sendConfigurationChangedEvent("cast_slideshow_repeat"); break; case R.id.play_pause: boolean isPlay = mPlayPauseDrawable.isPlay(); mPlayPauseDrawable.getPausePlayAnimator().start(); try { if (!isPlay) { // Pause mCastService.pause(); } else { // Play mCastService.resume(); } } catch (RemoteException ex) { Log.w(TAG, "Operation failed (isPlay: " + isPlay + ")", ex); } break; case R.id.previous: if (!mLoadingStatus) { try { mCastService.previous(); mLoadingStatus = true; } catch (RemoteException ex) { Log.w(TAG, "Operation failed (previous)", ex); mLoadingStatus = false; } } break; case R.id.next: if (!mLoadingStatus) { try { mCastService.next(); mLoadingStatus = true; } catch (RemoteException ex) { Log.w(TAG, "Operation failed (next)", ex); mLoadingStatus = false; } } break; } } private void sendConfigurationChangedEvent(String key) { Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); intent.putExtra(PreferencesProvider.EXTRA_FLAG_CAST_CONFIGURATION_CHANGE, Boolean.TRUE); intent.putExtra(PreferencesProvider.EXTRA_PREF_KEY, key); sendBroadcast(intent); } private void updateCurrentPlaying(String media) { if (media == null) { mTitle.setText(null); mAlbum.setText(null); mQueueAdapter.updateCurrent(null); mQueueAdapter.notifyDataSetChanged(); if (mPhoto.getAlpha() != 0.0f) { mPhoto.animate().alpha(0.0f).setDuration(250L).start(); } mLogo.animate().alpha(1.0f).setDuration(250L).start(); return; } final File f = new File(media); if (!f.exists() || !f.isFile()) { Log.w(TAG, "Media " + media + " doesn't exists"); return; } // Update the track info if (mLogo.getAlpha() != 0.0f) { mLogo.animate().alpha(0.0f).setDuration(250L).start(); } mPhoto.animate().alpha(0.2f).setDuration(250L).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { mTitle.setAlpha(0f); mAlbum.setAlpha(0f); } @Override public void onAnimationEnd(Animator animator) { mPhoto.animate().alpha(1.0f).setDuration(450L).setListener(null).start(); mPhoto.setImageBitmap( BitmapUtils.createUnscaledBitmap( f, mScreenDim.x, mScreenDim.y)); mTitle.setText(CastUtils.getTrackName(f)); mAlbum.setText(CastUtils.getAlbumName(f)); mTitle.setAlpha(1f); mAlbum.setAlpha(1f); mLoading.setVisibility(View.GONE); mLoadingStatus = false; } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }).start(); } private void updateTrackInfo() { if (mCastService != null) { try { final int mode = mCastService.getCurrentCastMode(); final String current = mCastService.getCurrentPlaying(); final boolean shuffle = PreferencesProvider.Preferences.Cast.isSlideshowShuffle(this); updateCurrentPlaying(current); mQueueAdapter.updateCurrent(current); mQueue.post(new Runnable() { @Override public void run() { int pos = mQueueList.indexOf(current) - 1; if (pos >= -1) { int size = (int) getResources().getDimension(R.dimen.queue_photo_size); int dx = size * pos; if (dx < 0) { dx = 0; } if (mode == CastService.CAST_MODE_SLIDESHOW && shuffle) { mQueue.scrollBy(dx - mOverallXScroll, 0); } else { mQueue.smoothScrollBy(dx - mOverallXScroll, 0); } mQueueAdapter.notifyDataSetChanged(); } } }); } catch (RemoteException rex) { //Ignore } } } private void refreshQueue(boolean notify) { if (mCastService != null) { try { if (notify) { final String current = mCastService.getCurrentPlaying(); updateCurrentPlaying(current); } synchronized (mQueueList) { mQueueList.clear(); String[] queue = mCastService.getCurrentQueue(); if (queue != null && queue.length > 0) { mQueueList.addAll(Arrays.asList(queue)); } } mQueueMediaPanel.setVisibility(mQueueList.size() > 0 ? View.VISIBLE : View.GONE); if (notify) { mQueueAdapter.notifyDataSetChanged(); } } catch (RemoteException rex) { //Ignore } } } private void showLoading() { mLoading.setVisibility(View.VISIBLE); mLoadingStatus = true; } }