/* ServiceListActivity.java Copyright (c) 2017 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.manager.setting; import android.animation.Animator; import android.app.Activity; import android.app.DialogFragment; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.support.v4.content.res.ResourcesCompat; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.GridView; import android.widget.ImageView; import android.widget.Switch; import android.widget.TextView; import org.deviceconnect.android.manager.BuildConfig; import org.deviceconnect.android.manager.DConnectApplication; import org.deviceconnect.android.manager.DConnectService; import org.deviceconnect.android.manager.DConnectSettings; import org.deviceconnect.android.manager.DevicePlugin; import org.deviceconnect.android.manager.DevicePluginManager; import org.deviceconnect.android.manager.R; import org.deviceconnect.android.manager.util.AnimationUtil; import org.deviceconnect.android.manager.util.DConnectUtil; import org.deviceconnect.android.manager.util.ServiceContainer; import org.deviceconnect.android.manager.util.ServiceDiscovery; import org.deviceconnect.android.profile.SystemProfile; import org.deviceconnect.message.intent.message.IntentDConnectMessage; import java.util.ArrayList; import java.util.List; /** * サービス一覧を表示するActivity. * * @author NTT DOCOMO, INC. */ public class ServiceListActivity extends Activity implements AlertDialogFragment.OnAlertDialogListener { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = "Manager"; /** * サービスがオフラインの時に表示するダイアログのタグ名を定義する. */ private static final String TAG_OFFLINE = "offline"; /** * ガイド用の設定を保存するファイル名を定義する. */ private static final String FILE_NAME = "__service_list__.dat"; /** * ガイド表示設定用のキーを定義する. */ private static final String KEY_SHOW_GUIDE = "show_guide"; /** * ガイド用のレイアウト一覧. */ private static final int[] GUIDE_ID_LIST = { R.id.activity_service_guide_1, R.id.activity_service_guide_2, }; /** * サービスアダプタ. */ private ServiceAdapter mServiceAdapter; /** * デバイスプラグイン管理クラス. */ private DevicePluginManager mDevicePluginManager; /** * ガイドの設定を保持するためのクラス. */ private SharedPreferences mSharedPreferences; /** * serviceDiscoveryを実行するクラス. */ private ServiceDiscovery mServiceDiscovery; /** * 選択されたサービスを一時的に格納する変数. */ private ServiceContainer mSelectedService; /** * ハンドラ. */ private Handler mHandler = new Handler(); /** * Device Connect Managerの起動スイッチ. */ private Switch mSwitchAction; /** * ガイドの表示ページ. */ private int mPageIndex; /** * Device Connect Managerの設定クラス. */ private DConnectSettings mSettings; /** * DConnectServiceのインスタンス. * <p> * バインドに成功するとDConnectServiceのインスタンスが格納される。<br> * バインドしていない場合には、{@code null}が格納される。 * </p> */ private DConnectService mDConnectService; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_service_list); mSettings = DConnectSettings.getInstance(); mSettings.load(this); mServiceAdapter = new ServiceAdapter(); GridView gridView = (GridView) findViewById(R.id.activity_service_list_grid_view); if (gridView != null) { gridView.setAdapter(mServiceAdapter); gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { openServiceInfo(position); } }); } Button btn = (Button) findViewById(R.id.activity_service_list_search_button); if (btn != null) { btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { reloadServiceList(); } }); } DConnectApplication app = (DConnectApplication) getApplication(); mDevicePluginManager = app.getDevicePluginManager(); if (loadGuideSettings(this)) { startGuide(); } } @Override public void onPause() { unbindService(mServiceConnection); super.onPause(); } @Override public void onResume() { super.onResume(); Intent intent = new Intent(getApplicationContext(), DConnectService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); if (!hasDevicePlugins()) { showNoDevicePlugin(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_service_list, menu); mSwitchAction = (Switch) menu.findItem(R.id.activity_service_manager_power).getActionView(); mSwitchAction.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { switchDConnectServer(isChecked); } }); if (mDConnectService != null) { mSwitchAction.setChecked(mDConnectService.isRunning()); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.activity_service_menu_item_settings) { openSettings(); } else if (id == R.id.activity_service_menu_item_help) { openHelp(); } return super.onOptionsItemSelected(item); } @Override public void onPositiveButton(final String tag) { if (TAG_OFFLINE.equals(tag)) { openPluginSettings(); } } @Override public void onNegativeButton(final String tag) { } private void setEnableSearchButton(final boolean running) { Button btn = (Button) findViewById(R.id.activity_service_list_search_button); if (btn != null) { if (mDConnectService != null) { btn.setEnabled(running); } } } /** * デバイスプラグインを保有しているかを確認する. * @return デバイスプラグインを保有している場合はtrue、それ以外はfalse */ private boolean hasDevicePlugins() { return mDevicePluginManager.getDevicePlugins().size() > 0; } /** * ガイド設定の読み込みを行う. * @param context コンテキスト * @return ガイドを表示する場合はtrue、それ以外はfalse */ private boolean loadGuideSettings(final Context context) { mSharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE); return mSharedPreferences.getBoolean(KEY_SHOW_GUIDE, true); } /** * ガイド設定の設定を行う. * @param showGuideFlag ガイドを表示する場合はtrue、それ以外はfalse */ private void saveGuideSettings(final boolean showGuideFlag) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(KEY_SHOW_GUIDE, showGuideFlag); editor.apply(); } /** * ガイドを表示を開始する. */ private void startGuide() { View guideView = findViewById(R.id.activity_service_guide); if (guideView != null) { guideView.setVisibility(View.VISIBLE); guideView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { nextGuide(); } }); } Button button = (Button) findViewById(R.id.activity_service_guide_button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextGuide(); } }); } /** * 次のガイドに移動する. */ private void nextGuide() { if (mPageIndex == GUIDE_ID_LIST.length - 1) { endGuide(); } else { animateGuide(new AnimationUtil.AnimationAdapter() { @Override public void onAnimationEnd(final Animator animation) { mPageIndex++; visibleGuide(); } }); } } /** * ガイドをアニメーションする. * @param listener アニメーション通知リスナー */ private void animateGuide(final AnimationUtil.AnimationAdapter listener) { for (int i = 0; i < GUIDE_ID_LIST.length; i++) { View view = findViewById(GUIDE_ID_LIST[i]); if (i == mPageIndex) { AnimationUtil.animateAlpha(view, listener); } } } /** * ガイドを表示する. */ private void visibleGuide() { for (int i = 0; i < GUIDE_ID_LIST.length; i++) { View view = findViewById(GUIDE_ID_LIST[i]); if (i == mPageIndex) { view.setVisibility(View.VISIBLE); AnimationUtil.animateAlpha2(view, new AnimationUtil.AnimationAdapter() { @Override public void onAnimationEnd(final Animator animation) { } }); } else { view.setVisibility(View.GONE); } } } /** * ガイドの終了処理を行う. */ private void endGuide() { boolean result = true; CheckBox checkBox = (CheckBox) findViewById(R.id.activity_service_guide_checkbox); if (checkBox != null) { result = !checkBox.isChecked(); } final View guideView = findViewById(R.id.activity_service_guide); if (guideView != null) { AnimationUtil.animateAlpha(guideView, new AnimationUtil.AnimationAdapter() { @Override public void onAnimationEnd(final Animator animation) { guideView.setVisibility(View.GONE); } }); } saveGuideSettings(result); } /** * DeviceServiceの起動を切り替える. * @param checked trueの場合は起動する、falseの場合は停止する */ private void switchDConnectServer(final boolean checked) { if (mDConnectService == null) { return; } if (mServiceDiscovery != null) { return; } mSwitchAction.setEnabled(false); mSettings.setManagerStartFlag(checked); if (checked) { mDConnectService.startInternal(); mHandler.postDelayed(new Runnable() { @Override public void run() { reloadServiceList(); mSwitchAction.setEnabled(true); } }, 500); } else { mDConnectService.stopInternal(); runOnUiThread(new Runnable() { @Override public void run() { mServiceAdapter.mServices = new ArrayList<>(); mServiceAdapter.notifyDataSetInvalidated(); mSwitchAction.setEnabled(true); } }); } setEnableSearchButton(checked); } /** * サービス一覧を再読み込みを行う. */ private void reloadServiceList() { if (DEBUG) { Log.i(TAG, "reloadServiceList a device plugin."); } // DConnectServiceが動作していない if (mDConnectService == null || !mDConnectService.isRunning()) { return; } // 既にserviceDiscoveryが実行されている場合 if (mServiceDiscovery != null) { return; } mServiceDiscovery = new ServiceDiscovery(this) { private DialogFragment mDialog; @Override protected void onPreExecute() { try { mDialog = new ServiceDiscoveryDialogFragment(); mDialog.show(getFragmentManager(), null); } catch (Exception e) { if (DEBUG) { Log.w(TAG, "Failed to open the dialog for service discovery."); } } } @Override protected void onPostExecute(final List<ServiceContainer> serviceContainers) { try { mDialog.dismiss(); View view = findViewById(R.id.activity_service_no_service); if (view != null) { view.setVisibility(serviceContainers.size() == 0 ? View.VISIBLE : View.GONE); } mServiceAdapter.mServices = serviceContainers; mServiceAdapter.notifyDataSetInvalidated(); } catch (Exception e) { if (DEBUG) { Log.w(TAG, "Failed to dismiss the dialog for service discovery."); } } finally { mServiceDiscovery = null; } } }; mServiceDiscovery.execute(); } /** * 設定画面を開く. */ private void openSettings() { Intent intent = new Intent(); intent.setClass(this, SettingActivity.class); startActivity(intent); } /** * ヘルプ画面を開く. */ private void openHelp() { String url = BuildConfig.URL_HELP_HTML; Intent intent = new Intent(); intent.setClass(this, WebViewActivity.class); intent.putExtra(WebViewActivity.EXTRA_URL, url); intent.putExtra(WebViewActivity.EXTRA_TITLE, getString(R.string.activity_help_title)); startActivity(intent); } /** * サービスの確認画面を開く. * @param position 開くサービスの */ private void openServiceInfo(final int position) { mSelectedService = (ServiceContainer) mServiceAdapter.getItem(position); if (mSelectedService.isOnline()) { String url = BuildConfig.URL_DEMO_HTML + "?serviceId=" + mSelectedService.getId(); Intent intent = new Intent(); intent.setClass(this, WebViewActivity.class); intent.putExtra(WebViewActivity.EXTRA_URL, url); intent.putExtra(WebViewActivity.EXTRA_TITLE, mSelectedService.getName()); startActivity(intent); } else { String title = getString(R.string.activity_service_list_offline_title); String message = getString(R.string.activity_service_list_offline_message, mSelectedService.getName()); String positive = getString(R.string.activity_service_list_offline_positive); String negative = getString(R.string.activity_service_list_offline_negative); AlertDialogFragment dialog = AlertDialogFragment.create(TAG_OFFLINE, title, message, positive, negative); dialog.show(getFragmentManager(), TAG_OFFLINE); } } /** * デバイスプラグインが一つもない場合のダイアログを表示する. */ private void showNoDevicePlugin() { String title = getString(R.string.activity_service_list_no_plugin_title); String message = getString(R.string.activity_service_list_no_plugin_message); String positive = getString(R.string.activity_service_list_no_plugin_positive); AlertDialogFragment dialog = AlertDialogFragment.create("no-device-plugin", title, message, positive); dialog.show(getFragmentManager(), "no-device-plugin"); } /** * デバイスプラグインの設定画面を開く. * <p> * {@link #mSelectedService}に格納されているデバイスプラグインの設定画面を開く。 * </p> */ private void openPluginSettings() { DConnectApplication app = (DConnectApplication) getApplication(); DevicePluginManager mgr = app.getDevicePluginManager(); List<DevicePlugin> plugins = mgr.getDevicePlugins(); for (DevicePlugin plugin : plugins) { if (mSelectedService.getId().contains(plugin.getPluginId())) { Intent request = new Intent(); request.setComponent(plugin.getComponentName()); request.setAction(IntentDConnectMessage.ACTION_PUT); SystemProfile.setApi(request, "gotapi"); SystemProfile.setProfile(request, SystemProfile.PROFILE_NAME); SystemProfile.setInterface(request, SystemProfile.INTERFACE_DEVICE); SystemProfile.setAttribute(request, SystemProfile.ATTRIBUTE_WAKEUP); request.putExtra("pluginId", plugin.getPluginId()); sendBroadcast(request); break; } } } /** * 指定されたサービスのパッケージ名を取得する. * @param serviceId サービスID * @return パッケージ名 */ private String getPackageName(final String serviceId) { List<DevicePlugin> list = mDevicePluginManager.getDevicePlugins(); for (DevicePlugin plugin : list) { if (serviceId.contains(plugin.getPluginId())) { return plugin.getPackageName(); } } return null; } /** * DConnectServiceとバインドするためのコネクションクラス. */ private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(final ComponentName name, final IBinder service) { mDConnectService = ((DConnectService.LocalBinder) service).getDConnectService(); runOnUiThread(new Runnable() { @Override public void run() { if (mSwitchAction != null) { mSwitchAction.setChecked(mDConnectService.isRunning()); } if (mDConnectService.isRunning()) { reloadServiceList(); } setEnableSearchButton(mDConnectService.isRunning()); } }); } @Override public void onServiceDisconnected(final ComponentName name) { mDConnectService = null; } }; /** * Device Connect Managerに接続されているサービスを表示するためのアダプタークラス. */ private class ServiceAdapter extends BaseAdapter { /** * サービス一覧. */ private List<ServiceContainer> mServices = new ArrayList<>(); @Override public int getCount() { return mServices.size(); } @Override public Object getItem(final int position) { return mServices.get(position); } @Override public long getItemId(final int position) { return position; } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View view = convertView; if (view == null) { view = getLayoutInflater().inflate(R.layout.item_service_list, null); } ServiceContainer service = (ServiceContainer) getItem(position); TextView textView = (TextView) view.findViewById(R.id.item_name); if (textView != null) { textView.setText(service.getName()); } ImageView typeView = (ImageView) view.findViewById(R.id.item_type); if (typeView != null) { switch(service.getNetworkType()) { case BLE: case BLUETOOTH: setNetworkTypeIcon(typeView, service, R.drawable.bluetooth_on); break; case WIFI: setNetworkTypeIcon(typeView, service, R.drawable.wifi_on); break; case NFC: setNetworkTypeIcon(typeView, service, R.drawable.nfc_on); break; default: typeView.setVisibility(View.GONE); break; } } ImageView imageView = (ImageView) view.findViewById(R.id.item_icon); if (imageView != null) { DConnectApplication app = (DConnectApplication) getApplication(); DevicePluginManager mgr = app.getDevicePluginManager(); List<DevicePlugin> plugins = mgr.getDevicePlugins(); for (DevicePlugin plugin : plugins) { if (service.getId().contains(plugin.getPluginId())) { setIcon(imageView, service, plugin.getPluginIcon()); break; } } } return view; } private void setNetworkTypeIcon(final ImageView imageView, final ServiceContainer service, final int resId) { setIcon(imageView, service, ResourcesCompat.getDrawable(getResources(),resId, null)); } private void setIcon(final ImageView imageView, final ServiceContainer service, final Drawable icon) { if (icon == null) { imageView.setVisibility(View.GONE); return; } Drawable newIcon = icon; if (!service.isOnline()) { newIcon = DConnectUtil.convertToGrayScale(icon); } else { newIcon.setColorFilter(null); } imageView.setVisibility(View.VISIBLE); imageView.setImageDrawable(newIcon); } } }