/* * Copyright (C) 2014 AChep@xda <artemchep@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.achep.acdisplay.ui.activities.settings; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.text.Html; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.SeekBar; import com.achep.acdisplay.Config; import com.achep.acdisplay.R; import com.achep.acdisplay.appwidget.MyAppWidgetHost; import com.achep.acdisplay.appwidget.MyAppWidgetHostView; import com.achep.acdisplay.ui.components.HostWidget; import com.achep.base.Device; import com.achep.base.content.ConfigBase; import com.achep.base.tests.Check; import com.achep.base.ui.SwitchBarPermissible; import com.achep.base.ui.activities.ActivityBase; import com.achep.base.ui.preferences.Enabler; import com.achep.base.ui.widgets.SwitchBar; import com.achep.base.utils.AppWidgetUtils; import com.achep.base.utils.MathUtils; import com.achep.base.utils.ToastUtils; import com.achep.base.utils.ViewUtils; import com.afollestad.materialdialogs.MaterialDialog; import com.melnykov.fab.FloatingActionButton; import java.util.ArrayList; /** * An activity for setting the custom App Widget, tweaking it * and licking. * * @author Artem Chepurnoy */ public class WidgetPickerActivity extends ActivityBase implements Config.OnConfigChangedListener, SeekBar.OnSeekBarChangeListener { private static final String TAG = "WidgetPickerActivity"; private static final String KEY_PENDING_APPWIDGET_ID = "achep::pending_app_widget_key"; /** * A request to open default AppWidget Picker dialog. */ private static final int REQUEST_APPWIDGET_DISCOVER = 1; /** * A request to open the configure activity of an AppWidget. */ private static final int REQUEST_APPWIDGET_CONFIGURE = 2; private static final int APPWIDGET_ID_NONE = -1; static { Check.getInstance().isFalse(AppWidgetUtils.isValidId(APPWIDGET_ID_NONE)); } private final Config mConfig = Config.getInstance(); private AppWidgetManager mAppWidgetManager; private MyAppWidgetHostView mHostView; private MyAppWidgetHost mAppWidgetHost; private ViewGroup mHostContainer; private int mPendingAppWidgetId = -1; private SwitchBarPermissible mSwitchPermissible; private MenuItem mConfigureMenuItem; private MenuItem mTouchableMenuItem; private MenuItem mClearMenuItem; private Enabler mEnabler; private View mEmptyView; // Adjust the width & height private SeekBar mWidthSeekBar; private View mWidthMessageView; private SeekBar mHeightSeekBar; private View mHeightMessageView; private int mMinWidth; private int mMinHeight; private FloatingActionButton mFab; private boolean mHostViewNeedsReInflate; private boolean mActivityResumed; @Override protected void onCreate(Bundle savedInstanceState) { if (mConfig.isWallpaperShown()) setTheme(R.style.MaterialTheme_WidgetPicker_Wallpaper); super.onCreate(savedInstanceState); setContentView(R.layout.activity_widget_picker); mEmptyView = findViewById(R.id.empty); mFab = (FloatingActionButton) findViewById(R.id.fab); mFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAppWidgetDiscover(); } }); mAppWidgetManager = AppWidgetManager.getInstance(this); mHostContainer = (ViewGroup) findViewById(R.id.appwidget_container); mAppWidgetHost = new MyAppWidgetHost(this, HostWidget.HOST_ID); initSwitchBar(); initSeekBars(); } private void initSwitchBar() { ActivityBase activity = (ActivityBase) getActivity(); SwitchBar switchBar = (SwitchBar) findViewById(R.id.switch_bar); mSwitchPermissible = new SwitchBarPermissible(activity, switchBar, null); mEnabler = new Enabler(this, mConfig, Config.KEY_UI_CUSTOM_WIDGET, mSwitchPermissible); } private void initSeekBars() { // Load the dimensions mMinHeight = getResources().getDimensionPixelSize(R.dimen.scene_min_height); int hMax = getResources().getDimensionPixelSize(R.dimen.scene_max_height); mMinWidth = getResources().getDimensionPixelSize(R.dimen.scene_min_width); int wMax = getResources().getDimensionPixelSize(R.dimen.scene_max_width); // Init views float progress, density = getResources().getDisplayMetrics().density; mWidthSeekBar = (SeekBar) findViewById(R.id.appwidget_width_seek_bar); mWidthSeekBar.setOnSeekBarChangeListener(this); mWidthSeekBar.setMax(wMax - mMinWidth); progress = MathUtils.range(mConfig.getCustomWidgetWidthDp() * density, mMinWidth, wMax); mWidthSeekBar.setProgress(Math.round(progress) - mMinWidth); mWidthMessageView = findViewById(R.id.appwidget_width_label); mHeightSeekBar = (SeekBar) findViewById(R.id.appwidget_height_seek_bar); mHeightSeekBar.setOnSeekBarChangeListener(this); mHeightSeekBar.setMax(hMax - mMinHeight); progress = MathUtils.range(mConfig.getCustomWidgetHeightDp() * density, mMinHeight, hMax); mHeightSeekBar.setProgress(Math.round(progress) - mMinHeight); mHeightMessageView = findViewById(R.id.appwidget_height_label); } @Override public void onStart() { super.onStart(); mAppWidgetHost.startListening(); updateAppWidgetViewIfNeeded(); } @Override protected void onResume() { super.onResume(); mActivityResumed = true; mSwitchPermissible.resume(); mEnabler.start(); mConfig.registerListener(this); } @Override protected void onPause() { mConfig.unregisterListener(this); mEnabler.stop(); mSwitchPermissible.pause(); mActivityResumed = false; super.onPause(); } @Override public void onStop() { mAppWidgetHost.stopListening(); mHostViewNeedsReInflate = true; // Stopping listening removes all active views from it, // so we will have to re-inflate them. super.onStop(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_PENDING_APPWIDGET_ID, mPendingAppWidgetId); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mPendingAppWidgetId = savedInstanceState.getInt(KEY_PENDING_APPWIDGET_ID, -1); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.widget_picker, menu); mClearMenuItem = menu.findItem(R.id.clear_action); mConfigureMenuItem = menu.findItem(R.id.configure_action); mTouchableMenuItem = menu.findItem(R.id.touchable); updateConfigureMenuItem(); updateTouchableMenuItem(); updateClearMenuItem(); return true; } /** * Updates the visibility of {@link #mClearMenuItem}. */ private void updateClearMenuItem() { if (mClearMenuItem == null) return; boolean visible = mHostView != null; mClearMenuItem.setVisible(visible); } /** * Updates the visibility of {@link #mConfigureMenuItem}. Shows if * the current widget has configure page, hides otherwise. */ private void updateConfigureMenuItem() { if (mClearMenuItem == null) return; boolean visible = mHostView != null && mHostView.getAppWidgetInfo() != null && mHostView.getAppWidgetInfo().configure != null; mConfigureMenuItem.setVisible(visible); } /** * Updates the visibility of {@link #mTouchableMenuItem}. */ private void updateTouchableMenuItem() { if (mTouchableMenuItem == null) return; boolean visible = mHostView != null; mTouchableMenuItem.setVisible(visible); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.clear_action: deleteCurrentAppWidget(); // Save the current change. storeAppWidget(APPWIDGET_ID_NONE); break; case R.id.configure_action: Check.getInstance().isNonNull(mHostView.getAppWidgetInfo().configure); startAppWidgetConfigure(mHostView.getAppWidgetInfo(), mHostView.getAppWidgetId()); break; case R.id.touchable: mTouchableMenuItem.setChecked(!mTouchableMenuItem.isChecked()); mConfig .getOption(Config.KEY_UI_CUSTOM_WIDGET_TOUCHABLE) .write(mConfig, this, mTouchableMenuItem.isChecked(), null); break; default: return super.onOptionsItemSelected(item); } return true; } /** * {@inheritDoc} */ @Override public void onConfigChanged(@NonNull ConfigBase config, @NonNull String key, @NonNull Object value) { switch (key) { case Config.KEY_UI_CUSTOM_WIDGET_ID: final int id = (int) value; if (mActivityResumed) { // Automatically turn the switch on/off, causing // terror and murders. mSwitchPermissible.setChecked(AppWidgetUtils.isValidId(id)); } mHostViewNeedsReInflate = true; updateAppWidgetViewIfNeeded(); break; case Config.KEY_UI_CUSTOM_WIDGET_WIDTH_DP: case Config.KEY_UI_CUSTOM_WIDGET_HEIGHT_DP: if (mHostView != null) updateAppWidgetFrameSize(); break; case Config.KEY_UI_CUSTOM_WIDGET_TOUCHABLE: if (mHostView != null) updateAppWidgetTouchable(); break; } } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (mHostView == null) return; updateAppWidgetFrameSize(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { /* unused */ } @Override public void onStopTrackingTouch(SeekBar seekBar) { double density = getResources().getDisplayMetrics().density; int widthDp = (int) ((mMinWidth + mWidthSeekBar.getProgress()) / density); int heightDp = (int) ((mMinHeight + mHeightSeekBar.getProgress()) / density); // Save new width or height to the config. The callback will // be ignored. mConfig.getOption(Config.KEY_UI_CUSTOM_WIDGET_WIDTH_DP).write(mConfig, this, widthDp, this); mConfig.getOption(Config.KEY_UI_CUSTOM_WIDGET_HEIGHT_DP).write(mConfig, this, heightDp, this); } /** * Updates the {@link #mHostView app widget's frame} size based on the * current width and height, specified by {@link #mWidthSeekBar}, {@link #mHeightSeekBar}. */ private void updateAppWidgetFrameSize() { int width = mMinWidth + mWidthSeekBar.getProgress(); int height = mMinHeight + mHeightSeekBar.getProgress(); ViewUtils.setSize(mHostView, width, height); mHostView.updateAppWidgetSize(null, width, height, width, height); } private void updateAppWidgetTouchable() { mHostView.setTouchable(mConfig.isCustomWidgetTouchable()); } /** * Writes the current widget to settings. * * @param id the id of app widget to add. */ private void storeAppWidget(int id) { mConfig.getOption(Config.KEY_UI_CUSTOM_WIDGET_ID).write(mConfig, this, id, null); } private void deleteCurrentAppWidgetSafely() { if (mHostView != null) deleteCurrentAppWidget(); } private void deleteCurrentAppWidget() { mHostContainer.removeView(mHostView); mAppWidgetHost.deleteAppWidgetId(mHostView.getAppWidgetId()); mHostView = null; } //-- DISCOVER and CONFIGURE ----------------------------------------------- @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { int id; switch (requestCode) { case REQUEST_APPWIDGET_DISCOVER: mPendingAppWidgetId = APPWIDGET_ID_NONE; if (data == null || data.getExtras() == null) { Check.getInstance().isFalse(resultCode == RESULT_OK); Log.i(TAG, "The intent data is empty."); break; } id = data.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); if (resultCode == RESULT_OK && AppWidgetUtils.isValidId(id)) { AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(id); if (appWidget == null) { // Clean-up allocated id. This is probably not needed, // cause we don't have an access to the widget. mAppWidgetHost.deleteAppWidgetId(id); // TODO: Toast a user about this incident. } else if (appWidget.configure != null) { mPendingAppWidgetId = id; startAppWidgetConfigure(appWidget, id); } else { // Just apply the widget. storeAppWidget(id); } } else { // Clean-up allocated id. mAppWidgetHost.deleteAppWidgetId(id); } break; case REQUEST_APPWIDGET_CONFIGURE: if (!AppWidgetUtils.isValidId(id = mPendingAppWidgetId)) break; mPendingAppWidgetId = APPWIDGET_ID_NONE; if (resultCode == RESULT_OK) { storeAppWidget(id); } else { // Clean-up allocated id. mAppWidgetHost.deleteAppWidgetId(id); } break; default: super.onActivityResult(requestCode, resultCode, data); } } /** * Launches the {@link AppWidgetManager#ACTION_APPWIDGET_PICK App Widget picker} * and wait for the result. */ private void startAppWidgetDiscover() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetHost.allocateAppWidgetId()); /* * This avoids a bug in the com.android.settings.AppWidgetPickActivity, which is used * to select widgets. This just adds empty extras to the intent, avoiding the bug. See * more: http://code.google.com/p/android/issues/detail?id=4272 */ ArrayList<Parcelable> customInfo = new ArrayList<>(0); intent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo); ArrayList<Parcelable> customExtras = new ArrayList<>(0); intent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras); startActivityForResult(intent, REQUEST_APPWIDGET_DISCOVER); } @SuppressWarnings("NewApi") private void startAppWidgetConfigure(@NonNull AppWidgetProviderInfo appWidget, int id) { try { if (Device.hasLollipopApi()) { mAppWidgetHost.startAppWidgetConfigureActivityForResult( this, id, 0, REQUEST_APPWIDGET_CONFIGURE, null); } else { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); intent.setComponent(appWidget.configure); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id); startActivityForResult(intent, REQUEST_APPWIDGET_CONFIGURE); } } catch (Exception e) { if (Device.isLge()) { CharSequence message = Html.fromHtml(getString(R.string.error_dialog_custom_widget_lg)); new MaterialDialog.Builder(this) .title(R.string.error_dialog_title) .content(message) .positiveText(android.R.string.ok) .show(); } else ToastUtils.showLong(this, R.string.error_dialog_title); } } //-- UPDATE USER INTERFACE ------------------------------------------------ private void updateAppWidgetViewIfNeeded() { int id = mConfig.getCustomWidgetId(); if (!AppWidgetUtils.isValidId(id)) { mHostViewNeedsReInflate = false; // Remove current app widget. deleteCurrentAppWidgetSafely(); // Update views mFab.show(); mEmptyView.setVisibility(View.VISIBLE); mWidthSeekBar.setVisibility(View.GONE); mWidthMessageView.setVisibility(View.GONE); mHeightSeekBar.setVisibility(View.GONE); mHeightMessageView.setVisibility(View.GONE); // Update menu updateConfigureMenuItem(); updateTouchableMenuItem(); updateClearMenuItem(); return; } if (mHostView == null) { mHostView = new MyAppWidgetHostView(this); mHostView.setBackgroundResource(R.drawable.bg_appwidget_preview); updateAppWidgetTouchable(); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL); mHostContainer.addView(mHostView, lp); } else if (!mHostViewNeedsReInflate && mHostView.getAppWidgetId() == id) return; AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(id); mAppWidgetHost.updateView(this, id, appWidget, mHostView); mHostViewNeedsReInflate = false; updateAppWidgetFrameSize(); // Update views mFab.hide(hasWindowFocus()); mEmptyView.setVisibility(View.GONE); mWidthSeekBar.setVisibility(View.VISIBLE); mWidthMessageView.setVisibility(View.VISIBLE); mHeightSeekBar.setVisibility(View.VISIBLE); mHeightMessageView.setVisibility(View.VISIBLE); // Update menu mHostView.post(new Runnable() { @Override public void run() { updateConfigureMenuItem(); updateTouchableMenuItem(); updateClearMenuItem(); } }); } }