/* * Copyright 2015. Appsi Mobile * * 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.appsimobile.appsii.module.home.appwidget; import android.app.Activity; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.util.SimpleArrayMap; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.appsimobile.appsii.BuildConfig; import com.appsimobile.appsii.R; import com.appsimobile.appsii.appwidget.AppWidgetUtils; import com.appsimobile.appsii.compat.AppWidgetManagerCompat; import com.appsimobile.appsii.dagger.AppInjector; import com.appsimobile.appsii.module.home.config.HomeItemConfiguration; import java.text.Collator; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.inject.Inject; /** * The activity that lets user select and configure a widget to add to a cell. * <p/> * Created by nick on 19/02/15. */ public class WidgetChooserActivity extends Activity implements WidgetViewHolder.OnWidgetClickedListener, View.OnClickListener { /** * The extra describing the cell id. This is the cell to which * the widget will be added. */ public static final String EXTRA_CELL_ID = BuildConfig.APPLICATION_ID + ".cell_id"; /** * The request to bind the app-widget, if the user has not allowed this. */ private static final int REQUEST_BIND_APPWIDGET = 105; /** * The configuration request. Used to set the widget options. */ private static final int REQUEST_CONFIGURE_APPWIDGET = 106; /** * The ok-button. This binds the app-widget, and starts the configuration * activity if needed. This button is only enabled when a view is selected */ View mOkButton; /** * Pressing the cancel button will finish the activity */ View mCancelButton; /** * The cell that is being edited */ long mCellId; /** * The recycler-view showing the list of app-widgets the user can choose from. */ RecyclerView mRecyclerView; /** * The decoration that draws the selection and adds spacing to the elements */ SingleSelectionDecoration mSingleSelectionDecoration; /** * The currently selected info. This is retained in the instance state */ AppWidgetProviderInfo mSelectedAppWidgetProviderInfo; /** * The appWidgetHost. Used to allocate app-widget-ids */ @Inject AppWidgetHost mAppWidgetHost; /** * The currently allocated app-widget-id for which we are binding or configuring. * Retained in instance state. */ int mPendingAddWidgetId; /** * The widget manager. Used to perform most of the operations on compatible with * multiple api levels. */ @Inject AppWidgetManagerCompat mAppWidgetManager; @Inject HomeItemConfiguration mHomeItemConfiguration; @Inject AppWidgetUtils mAppWidgetUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AppInjector.inject(this); setContentView(R.layout.fragment_widget_chooser); mOkButton = findViewById(R.id.ok_button); mCancelButton = findViewById(R.id.cancel); mRecyclerView = (RecyclerView) findViewById(R.id.widget_recycler); mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2)); mOkButton.setOnClickListener(this); mCancelButton.setOnClickListener(this); mSingleSelectionDecoration = new SingleSelectionDecoration(this); mRecyclerView.addItemDecoration(mSingleSelectionDecoration); List<AppWidgetProviderInfo> appWidgetProviders = mAppWidgetUtils .loadAppWidgetProviderInfos(); Collections.sort(appWidgetProviders, new WidgetNameComparator(this, mAppWidgetManager)); WidgetAdapter adapter = new WidgetAdapter(appWidgetProviders, this, mAppWidgetUtils); mRecyclerView.setAdapter(adapter); mCellId = getIntent().getLongExtra(EXTRA_CELL_ID, -1); // restore the state and the selection if (savedInstanceState != null) { mPendingAddWidgetId = savedInstanceState.getInt("pendingAppWidgetId"); mSelectedAppWidgetProviderInfo = savedInstanceState.getParcelable("providerInfo"); int position = savedInstanceState.getInt("selection"); mSingleSelectionDecoration.setSelectedPosition(position); } // disable the ok-button when nothing is selected. Useful in // case we where resumed with an active selection, or started // without a selection if (mSingleSelectionDecoration.mSelectedPosition == -1) { mOkButton.setEnabled(false); } } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable("providerInfo", mSelectedAppWidgetProviderInfo); outState.putInt("selection", mSingleSelectionDecoration.mSelectedPosition); outState.putInt("pendingAppWidgetId", mPendingAddWidgetId); } @Override public void finish() { super.finish(); } @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { // Handle binding app widgets final int pendingAddWidgetId = mPendingAddWidgetId; if (requestCode == REQUEST_BIND_APPWIDGET) { final int appWidgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; if (resultCode == RESULT_CANCELED) { mAppWidgetHost.deleteAppWidgetId(mPendingAddWidgetId); } else if (resultCode == RESULT_OK) { onAppWidgetSelected(appWidgetId, mSelectedAppWidgetProviderInfo); } return; } // handle configuration of app-widgets boolean isAppWidgetConfig = requestCode == REQUEST_CONFIGURE_APPWIDGET; if (isAppWidgetConfig) { int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; // get the appWidgetId falling back to the pendingId final int appWidgetId; if (widgetId < 0) { appWidgetId = pendingAddWidgetId; } else { appWidgetId = widgetId; } // The appwidget has been configured. or the user cancelled the process. // When cancelled delete the app-widget-id and clear the pending id. if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { Log.e("WidgetChooser", "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + "returned from the widget configuration activity."); mAppWidgetHost.deleteAppWidgetId(mPendingAddWidgetId); mPendingAddWidgetId = -1; } else { // save the widget id to the cell. finishAndSaveWidgetToCell(appWidgetId); } return; } super.onActivityResult(requestCode, resultCode, data); } @Override public void onWidgetClicked(AppWidgetProviderInfo info, WidgetViewHolder viewHolder) { int position = viewHolder.getPosition(); if (mSingleSelectionDecoration.toggleSelection(position)) { mOkButton.setEnabled(true); mSelectedAppWidgetProviderInfo = info; } else { mOkButton.setEnabled(false); mSelectedAppWidgetProviderInfo = null; } } @Override public void onClick(View v) { int viewId = v.getId(); if (viewId == R.id.ok_button) { if (mSelectedAppWidgetProviderInfo != null) { AppWidgetProviderInfo info = mSelectedAppWidgetProviderInfo; // In this case, we either need to start an activity to get permission to bind // the widget, or we need to start an activity to configure the widget, or both. mPendingAddWidgetId = mAppWidgetHost.allocateAppWidgetId(); boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(mPendingAddWidgetId, info, null); if (success) { onAppWidgetSelected(mPendingAddWidgetId, info); } else { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingAddWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider); mAppWidgetManager.getUser(mSelectedAppWidgetProviderInfo) .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE); // TODO: we need to make sure that this accounts for the options bundle. // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, // (Parcelable) null); startActivityForResult(intent, REQUEST_BIND_APPWIDGET); } } } else if (viewId == R.id.cancel) { finish(); } } /** * This is called when the app-widget was successfully bound. This method * start the configuration activity if needed. If this is not needed the * widget is added to the cell. */ void onAppWidgetSelected(final int appWidgetId, final AppWidgetProviderInfo appWidgetInfo) { if (appWidgetInfo.configure != null) { mPendingAddWidgetId = appWidgetId; // Launch over to configure widget, if needed mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this, mAppWidgetHost, REQUEST_CONFIGURE_APPWIDGET); } else { // Otherwise just add it finishAndSaveWidgetToCell(appWidgetId); } } /** * Add a widget to the cell we are editing. */ private void finishAndSaveWidgetToCell(final int appWidgetId) { mHomeItemConfiguration .updateProperty(mCellId, "app_widget_id", String.valueOf(appWidgetId)); finish(); } static class WidgetAdapter extends RecyclerView.Adapter<WidgetViewHolder> { final List<AppWidgetProviderInfo> mAppWidgetProviders; final WidgetViewHolder.OnWidgetClickedListener mOnWidgetClickedListener; final AppWidgetUtils mAppWidgetUtils; WidgetAdapter(List<AppWidgetProviderInfo> appWidgetProviders, WidgetViewHolder.OnWidgetClickedListener onWidgetClickedListener, AppWidgetUtils appWidgetUtils) { mAppWidgetProviders = appWidgetProviders; mOnWidgetClickedListener = onWidgetClickedListener; mAppWidgetUtils = appWidgetUtils; } @Override public WidgetViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.grid_item_widget, parent, false); return new WidgetViewHolder(view, mAppWidgetUtils, mOnWidgetClickedListener); } @Override public void onBindViewHolder(WidgetViewHolder holder, int position) { AppWidgetProviderInfo info = mAppWidgetProviders.get(position); holder.bind(info); } @Override public int getItemCount() { return mAppWidgetProviders.size(); } } public static class WidgetNameComparator implements Comparator<AppWidgetProviderInfo> { private final AppWidgetManagerCompat mManager; private final PackageManager mPackageManager; private final SimpleArrayMap<Object, String> mLabelCache; private final Collator mCollator; WidgetNameComparator(Context context, AppWidgetManagerCompat awm) { mManager = awm; mPackageManager = context.getPackageManager(); mLabelCache = new SimpleArrayMap<>(); mCollator = Collator.getInstance(); } public final int compare(AppWidgetProviderInfo lhs, AppWidgetProviderInfo rhs) { String labelA, labelB; if (mLabelCache.containsKey(lhs)) { labelA = mLabelCache.get(lhs); } else { labelA = mManager.loadLabel(lhs); mLabelCache.put(lhs, labelA); } if (mLabelCache.containsKey(rhs)) { labelB = mLabelCache.get(rhs); } else { labelB = mManager.loadLabel(rhs); mLabelCache.put(rhs, labelB); } return mCollator.compare(labelA, labelB); } } class SingleSelectionDecoration extends RecyclerView.ItemDecoration { final Paint mSelectionOutlinePaint; final Rect mRect = new Rect(); final RectF mRectf = new RectF(); final float mCornerRadius; private final int[] ATTRS = new int[]{ R.attr.colorAccent, R.attr.appsiSidebarBackground, }; int mSelectedPosition = -1; SingleSelectionDecoration(Context context) { mCornerRadius = context.getResources().getDisplayMetrics().density * 1; float strokeWidth = context.getResources().getDisplayMetrics().density * 2; final TypedArray a = context.obtainStyledAttributes(ATTRS); int accentColor = a.getColor(0, Color.WHITE); a.recycle(); mSelectionOutlinePaint = new Paint(); mSelectionOutlinePaint.setStyle(Paint.Style.STROKE); mSelectionOutlinePaint.setStrokeWidth(strokeWidth); mSelectionOutlinePaint.setColor(accentColor); } public void setSelectedPosition(int selectedPosition) { mSelectedPosition = selectedPosition; mRecyclerView.invalidate(); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); if (mSelectedPosition == -1) return; int count = parent.getChildCount(); for (int i = 0; i < count; i++) { View child = parent.getChildAt(i); int position = parent.getChildLayoutPosition(child); mRect.set(0, 0, child.getWidth(), child.getHeight()); parent.offsetDescendantRectToMyCoords(child, mRect); mRectf.set(mRect); if (position == mSelectedPosition) { c.drawRoundRect(mRectf, mCornerRadius, mCornerRadius, mSelectionOutlinePaint); } } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int offset = (int) (2 * getResources().getDisplayMetrics().density); outRect.set(offset, offset, offset, offset); } boolean toggleSelection(int selection) { if (mSelectedPosition == selection) { mSelectedPosition = -1; return false; } mSelectedPosition = selection; mRecyclerView.invalidate(); return true; } } }