/*
* 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();
}
});
}
}