package com.ruesga.android.wallpapers.photophase.cast; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import com.ruesga.android.wallpapers.photophase.AndroidHelper; import com.ruesga.android.wallpapers.photophase.R; import com.ruesga.android.wallpapers.photophase.preferences.PreferencesProvider.Preferences.Cast; import java.util.ArrayList; import java.util.Collections; import java.util.List; import su.litvak.chromecast.api.v2.ChromeCast; public class CastRouteActivity extends AppCompatActivity { private static final String TAG = "CastRouteActivity"; private class DeviceAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final Context mContext; private boolean mSeeking; private final List<ChromeCast> mDevices; public DeviceAdapter(Context context, List<ChromeCast> devices, boolean seeking) { mContext = context; mDevices = devices; mSeeking = seeking; } private class ProgressBarViewHolder extends RecyclerView.ViewHolder { public ProgressBarViewHolder(View view) { super(view); } } private class DeviceViewHolder extends RecyclerView.ViewHolder { public DeviceViewHolder(View view) { super(view); } } public void stopSeeking() { if (mSeeking) { mSeeking = false; notifyDataSetChanged(); } } @Override public int getItemViewType(int position) { if (position >= mDevices.size()) { return 1; } return 2; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final LayoutInflater li = LayoutInflater.from(mContext); switch (viewType) { case 1: return new ProgressBarViewHolder( li.inflate(R.layout.cast_device_waiting, parent, false)); default: return new DeviceViewHolder( li.inflate(R.layout.cast_device_item, parent, false)); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position < mDevices.size()) { ChromeCast device = mDevices.get(position); TextView tv = (TextView) holder.itemView; if (device.getName() == null) { tv.setText(R.string.cast_dialog_no_devices_found); tv.setCompoundDrawables(null, null, null, null); tv.setGravity(Gravity.CENTER); tv.setTag(-1); } else { tv.setText(device.getName()); tv.setTag(position); tv.setOnClickListener(mOnItemClickListener); } } } @Override public int getItemCount() { return mDevices.size() + (mSeeking ? 1 : 0); } } private final View.OnClickListener mOnItemClickListener = new View.OnClickListener() { @Override public void onClick(View view) { int position = (Integer) view.getTag(); if (position >= 0) { onDeviceSelected(mDevices.get(position)); } } }; private final AsyncTask<Void, ChromeCast, Void> mDeviceSeekTask = new AsyncTask<Void, ChromeCast, Void>() { private final Object mLock = new Object(); @Override protected void onPreExecute() { // Add the last discovered devices List<ChromeCast> storedDevices = Cast.getLastDiscoveredDevices(CastRouteActivity.this); if (storedDevices != null) { mDevices.addAll(storedDevices); mAdapter.notifyDataSetChanged(); } } @Override protected Void doInBackground(Void... voids) { final Context ctx = CastRouteActivity.this; // NsdManager is not supported in api 15 or lower if (!AndroidHelper.isJellyBeanOrGreater()) { Log.d(TAG, "Cast is not supported"); return null; } // ChromeCast only works on wifi networks, so it doesn't made sense to // try to seek devices in the current network if it isn't a wifi network if (!CastUtils.hasValidCastNetwork(CastRouteActivity.this)) { Log.d(TAG, "Not active cast network"); return null; } final CastDiscover discover = new CastDiscover(ctx, new CastDiscover.DeviceResolverListener() { @Override public void onDeviceDiscovered(ChromeCast device) { if (isNewDevice(device)) { Log.d(TAG, "Found device " + device.getName() + " at " + device.getAddress() + ":" + device.getPort()); publishProgress(device); } } }); Log.d(TAG, "Start discovering new devices"); discover.startDiscovery(); mSeeking = true; // Wait for a seconds while seeking the network synchronized (mLock) { try { mLock.wait(Cast.getDiscoveryTime(ctx) * 1000L); } catch (InterruptedException e) { // Ignore } } Log.d(TAG, "Exit wait"); // Done. We should have all available devices mSeeking = false; discover.stopDiscovery(); Log.d(TAG, "Stop discovering new devices"); return null; } @Override protected void onCancelled() { notifyStop(); synchronized (mLock) { mLock.notify(); } } @Override protected void onPostExecute(Void v) { notifyStop(); } @Override protected void onProgressUpdate(ChromeCast... devices) { Collections.addAll(mLastDiscoveredDevices, devices); for (ChromeCast device : devices) { if (!mDevices.contains(device)) { mDevices.add(device); mAdapter.notifyItemInserted(mDevices.size() - 1); } else { int index = mDevices.indexOf(device); mDevices.set(index, device); mAdapter.notifyItemChanged(index); } } } private void notifyStop() { // Save the last discovered devices Cast.setLastDiscoveredDevices(CastRouteActivity.this, mLastDiscoveredDevices); // Stop seeking if (mDevices.size() == 0) { // Add an empty device to advise the user that there is no devices mDevices.add(new ChromeCast("")); } mAdapter.stopSeeking(); } }; private final List<ChromeCast> mDevices = new ArrayList<>(); private final List<ChromeCast> mLastDiscoveredDevices = new ArrayList<>(); private AlertDialog mDialog; private DeviceAdapter mAdapter; private boolean mSeeking; private String mPath; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSeeking = true; if (savedInstanceState != null) { mSeeking = savedInstanceState.getBoolean("cast.seeking", true); int count = savedInstanceState.getInt("cast.count", 0); for (int i = 0; i < count; i++) { String deviceInfo = savedInstanceState.getString("cast." + i + ".device"); if (deviceInfo != null) { ChromeCast device = CastUtils.string2chromecast(deviceInfo); mDevices.add(device); } } mPath = savedInstanceState.getString(CastService.EXTRA_PATH); } else { // Check if it was routed. In that case just show a toast boolean routed = getIntent().getBooleanExtra(CastService.EXTRA_ROUTED, false); if (routed) { ChromeCast device = Cast.getLastConnectedDevice(this); String msg = getString(R.string.cast_dialog_sent_to_device, (device != null) ? device.getName() : "-"); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); finish(); return; } // Recovery the path mPath = getIntent().getStringExtra(CastService.EXTRA_PATH); // An error happened? Notify the user boolean isError = getIntent().getBooleanExtra(CastService.EXTRA_IS_ERROR, false); if (isError) { Toast.makeText(this, R.string.cast_connect_error, Toast.LENGTH_SHORT).show(); } } createDialog(); } @Override protected void onDestroy() { super.onDestroy(); if (mDeviceSeekTask.getStatus().equals(AsyncTask.Status.RUNNING)) { mDeviceSeekTask.cancel(true); } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); showDialog(); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(CastService.EXTRA_PATH, mPath); outState.putBoolean("seeking", mSeeking); int i = 0; outState.putInt("cast.count", mDevices.size()); for (ChromeCast device : mDevices) { outState.putString("cast." + i + ".device", CastUtils.chromecast2string(device)); i++; } } @SuppressLint("InflateParams") private void createDialog() { // Configure the devices list view final LayoutInflater li = LayoutInflater.from(this); RecyclerView view = (RecyclerView) li.inflate(R.layout.cast_device_dialog, null, false); view.setHasFixedSize(false); LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); view.setLayoutManager(layoutManager); mAdapter = new DeviceAdapter(this, mDevices, mSeeking); view.setAdapter(mAdapter); // Configure the dialog AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.cast_dialog_title); builder.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialogInterface) { if (mDialog != null) { mDialog = null; finish(); } } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialogInterface) { mDialog = null; finish(); } }); builder.setView(view); mDialog = builder.create(); } private void showDialog() { mDialog.show(); mDeviceSeekTask.execute(); } private void onDeviceSelected(ChromeCast device) { // Interrupt the background task if (!mDeviceSeekTask.getStatus().equals(AsyncTask.Status.FINISHED)) { mDeviceSeekTask.cancel(true); } // Dismiss the dialog if (mDialog != null) { mDialog.dismiss(); } Log.d(TAG, "Selected device " + device.getName() + " at " + device.getAddress() + ":" + device.getPort()); // Initialize the cast service with this device Intent i = new Intent(this, CastService.class); i.setAction(CastService.ACTION_DEVICE_SELECTED); i.putExtra(CastService.EXTRA_PATH, mPath); i.putExtra(CastService.EXTRA_DEVICE, device.getAddress() + ":" + device.getPort() + "/" + device.getName()); startService(i); } private boolean isNewDevice(ChromeCast device) { for (ChromeCast cc : mDevices) { if (cc.getAddress().equals(device.getAddress()) && cc.getPort() == device.getPort()) { return false; } } return true; } }