/*
* Copyright 2013 Google Inc.
*
* 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.google.android.apps.dashclock.configuration;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.google.android.apps.dashclock.DashClockService;
import com.google.android.apps.dashclock.ExtensionManager;
import com.google.android.apps.dashclock.ExtensionSettingActivityProxy;
import com.google.android.apps.dashclock.Utils;
import com.google.android.apps.dashclock.api.host.ExtensionListing;
import com.google.android.apps.dashclock.ui.SwipeDismissListViewTouchListener;
import com.google.android.apps.dashclock.ui.UndoBarController;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import net.nurik.roman.dashclock.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Fragment for allowing the user to configure active extensions, shown within a {@link
* ConfigurationActivity}.
*/
public class ConfigureExtensionsFragment extends Fragment implements
ExtensionManager.OnChangeListener,
UndoBarController.UndoListener {
private static final String SAVE_KEY_SELECTED_EXTENSIONS = "selected_extensions";
private ExtensionManager mExtensionManager;
private List<ComponentName> mSelectedExtensions = new ArrayList<>();
private ExtensionListAdapter mSelectedExtensionsAdapter;
private Map<ComponentName, ExtensionListing> mExtensionListings = new HashMap<>();
private Map<ComponentName, BitmapDrawable> mExtensionIcons = new HashMap<>();
private List<ComponentName> mAvailableExtensions = new ArrayList<>();
private PopupMenu mAddExtensionPopupMenu;
private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
repopulateAvailableExtensions();
}
};
private DragSortListView mListView;
private SwipeDismissListViewTouchListener mSwipeDismissTouchListener;
private UndoBarController mUndoBarController;
private View mAddFabView;
public ConfigureExtensionsFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set up helper components
mExtensionManager = ExtensionManager.getInstance(getActivity());
mExtensionManager.addOnChangeListener(this);
if (savedInstanceState == null) {
// TODO: should load in the correct order
mSelectedExtensions = mExtensionManager.getInternalActiveExtensionNames();
} else {
List<String> selected = savedInstanceState
.getStringArrayList(SAVE_KEY_SELECTED_EXTENSIONS);
for (String s : selected) {
mSelectedExtensions.add(ComponentName.unflattenFromString(s));
}
}
mSelectedExtensionsAdapter = new ExtensionListAdapter();
repopulateAvailableExtensions();
IntentFilter packageChangeIntentFilter = new IntentFilter();
packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageChangeIntentFilter.addDataScheme("package");
getActivity().registerReceiver(mPackageChangedReceiver, packageChangeIntentFilter);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_configure_extensions, container, false);
mListView = (DragSortListView) rootView.findViewById(android.R.id.list);
mListView.setAdapter(mSelectedExtensionsAdapter);
mListView.setEmptyView(rootView.findViewById(android.R.id.empty));
final ConfigurationDragSortController dragSortController =
new ConfigurationDragSortController();
mListView.setFloatViewManager(dragSortController);
mListView.setDropListener(new DragSortListView.DropListener() {
@Override
public void drop(int from, int to) {
ComponentName name = mSelectedExtensions.remove(from);
mSelectedExtensions.add(to, name);
mSelectedExtensionsAdapter.notifyDataSetChanged();
mExtensionManager.setInternalActiveExtensions(mSelectedExtensions);
}
});
mSwipeDismissTouchListener = new SwipeDismissListViewTouchListener(
mListView,
new SwipeDismissListViewTouchListener.DismissCallbacks() {
public boolean canDismiss(int position) {
return true;
}
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
showRemoveUndoBar(reverseSortedPositions);
for (int position : reverseSortedPositions) {
mSelectedExtensions.remove(position);
}
repopulateAvailableExtensions();
mSelectedExtensionsAdapter.notifyDataSetChanged();
mExtensionManager.setInternalActiveExtensions(mSelectedExtensions);
}
});
mListView.setOnScrollListener(mSwipeDismissTouchListener.makeScrollListener());
mListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
mUndoBarController.hideUndoBar(false);
}
return dragSortController.onTouch(view, motionEvent)
|| (!dragSortController.isDragging()
&& mSwipeDismissTouchListener.onTouch(view, motionEvent));
}
});
mListView.setItemsCanFocus(true);
mAddFabView = rootView.findViewById(R.id.add_extension_fab);
mAddFabView.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
showAddExtensionMenu(view);
}
});
showHideAddFabView(false);
mUndoBarController = new UndoBarController(rootView.findViewById(R.id.undobar), this);
mUndoBarController.onRestoreInstanceState(savedInstanceState);
return rootView;
}
@Override
public void onPause() {
super.onPause();
mExtensionManager.setInternalActiveExtensions(mSelectedExtensions);
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().unregisterReceiver(mPackageChangedReceiver);
mExtensionManager.removeOnChangeListener(this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ArrayList<String> selectedExtensions = new ArrayList<>();
for (ComponentName cn : mSelectedExtensions) {
selectedExtensions.add(cn.flattenToString());
}
outState.putStringArrayList(SAVE_KEY_SELECTED_EXTENSIONS, selectedExtensions);
if (mUndoBarController != null) {
mUndoBarController.onSaveInstanceState(outState);
}
}
private void repopulateAvailableExtensions() {
Set<ComponentName> selectedExtensions = new HashSet<>();
selectedExtensions.addAll(mSelectedExtensions);
boolean selectedExtensionsDirty = false;
mExtensionListings.clear();
mAvailableExtensions.clear();
Resources res = getResources();
for (ExtensionListing listing : mExtensionManager.getAvailableExtensions()) {
ComponentName extension = listing.componentName();
mExtensionListings.put(listing.componentName(), listing);
ExtensionListing previousListing = mExtensionListings.put(extension, listing);
if (listing.icon() != 0) {
Bitmap icon = Utils.loadExtensionIcon(getActivity(), extension,
listing.icon(), null, res.getColor(R.color.extension_item_color));
mExtensionIcons.put(extension, new BitmapDrawable(res, icon));
}
if (selectedExtensions.contains(listing.componentName())) {
if (previousListing == null) {
// Extension was newly detected
selectedExtensionsDirty = true;
}
if (!listing.compatible()) {
// If the extension is selected and its protocol isn't supported,
// then make it available but remove it from the selection.
mSelectedExtensions.remove(listing.componentName());
selectedExtensionsDirty = true;
} else {
// If it's selected and supported, don't add it to the list of available
// extensions.
continue;
}
}
mAvailableExtensions.add(listing.componentName());
}
Collections.sort(mAvailableExtensions, new Comparator<ComponentName>() {
@Override
public int compare(ComponentName cn1, ComponentName cn2) {
ExtensionListing listing1 = mExtensionListings.get(cn1);
ExtensionListing listing2 = mExtensionListings.get(cn2);
return listing1.title().compareToIgnoreCase(listing2.title());
}
});
showHideAddFabView(true);
if (selectedExtensionsDirty && mSelectedExtensionsAdapter != null) {
mSelectedExtensionsAdapter.notifyDataSetChanged();
mExtensionManager.setInternalActiveExtensions(mSelectedExtensions);
}
if (mAddExtensionPopupMenu != null) {
mAddExtensionPopupMenu.dismiss();
mAddExtensionPopupMenu = null;
}
}
private void showHideAddFabView(boolean animate) {
if (mAddFabView == null) {
return;
}
final boolean show = mAvailableExtensions.size() > 0;
Boolean lastValue = (Boolean) mAddFabView.getTag();
if (lastValue == null || lastValue != show) {
if (show) {
mAddFabView.setVisibility(View.VISIBLE);
}
mAddFabView.animate()
.setDuration(animate ? 200 : 0)
.scaleX(show ? 1 : 0)
.scaleY(show ? 1 : 0)
.withEndAction(new Runnable() {
@Override
public void run() {
if (!show) {
mAddFabView.setVisibility(View.GONE);
}
}
});
}
}
private void showAddExtensionMenu(View anchorView) {
if (mAddExtensionPopupMenu != null) {
mAddExtensionPopupMenu.dismiss();
}
mAddExtensionPopupMenu = new PopupMenu(getActivity(), anchorView);
for (int i = 0; i < mAvailableExtensions.size(); i++) {
ComponentName cn = mAvailableExtensions.get(i);
ExtensionListing listing = mExtensionListings.get(cn);
String label = (listing == null) ? null : listing.title();
if (TextUtils.isEmpty(label)) {
label = cn.flattenToShortString();
}
if (listing != null && !listing.compatible()) {
label = getString(R.string.incompatible_extension_menu_template, label);
}
mAddExtensionPopupMenu.getMenu().add(Menu.NONE, i, Menu.NONE, label);
}
mAddExtensionPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
mAddExtensionPopupMenu.dismiss();
mAddExtensionPopupMenu = null;
ComponentName cn = mAvailableExtensions.get(menuItem.getItemId());
ExtensionListing extensionListing = mExtensionListings.get(cn);
if (extensionListing == null || !extensionListing.compatible()) {
String title = cn.getShortClassName();
if (extensionListing != null) {
title = extensionListing.title();
}
CantAddExtensionDialog
.createInstance(cn.getPackageName(), title)
.show(getFragmentManager(), "cantaddextension");
return true;
}
// Item id == position for this popup menu.
mSelectedExtensions.add(cn);
repopulateAvailableExtensions();
mSelectedExtensionsAdapter.notifyDataSetChanged();
mListView.smoothScrollToPosition(mSelectedExtensionsAdapter.getCount() - 1);
return true;
}
});
mAddExtensionPopupMenu.show();
}
private class ConfigurationDragSortController extends DragSortController {
private int mPos;
private boolean mDragging;
public ConfigurationDragSortController() {
super(ConfigureExtensionsFragment.this.mListView, R.id.drag_handle,
DragSortController.ON_DOWN, 0);
setRemoveEnabled(false);
mDragging = false;
}
public boolean isDragging() {
return mDragging;
}
@Override
public boolean startDrag(int position, int deltaX, int deltaY) {
mDragging = super.startDrag(position, deltaX, deltaY);
return mDragging;
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean ret = super.onTouch(v, ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDragging = false;
break;
}
return ret;
}
@Override
public View onCreateFloatView(int position) {
Vibrator v = (Vibrator) ConfigureExtensionsFragment.this.mListView
.getContext().getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(10);
mPos = position;
return mSelectedExtensionsAdapter.getView(position, null, mListView);
}
@Override
public void onDestroyFloatView(View floatView) {
//do nothing; block super from crashing
}
}
@Override
public void onExtensionsChanged(ComponentName sourceExtension) {
repopulateAvailableExtensions();
}
private void showRemoveUndoBar(int[] reverseSortedPositions) {
String undoString;
if (reverseSortedPositions.length == 1) {
ExtensionListing listing = mExtensionListings.get(
mSelectedExtensions.get(reverseSortedPositions[0]));
undoString = getString(R.string.extension_removed_template,
(listing != null) ? listing.title() : "??");
} else {
undoString = getString(R.string.extensions_removed_template,
reverseSortedPositions.length);
}
ComponentName[] extensions = new ComponentName[reverseSortedPositions.length];
for (int i = 0; i < reverseSortedPositions.length; i++) {
extensions[i] = mSelectedExtensions.get(reverseSortedPositions[i]);
}
Bundle undoBundle = new Bundle();
undoBundle.putIntArray("positions", reverseSortedPositions);
undoBundle.putParcelableArray("extensions", extensions);
mUndoBarController.showUndoBar(
false,
undoString,
undoBundle);
}
@Override
public void onUndo(Bundle token) {
if (token == null) {
return;
}
// Perform the undo
int[] reverseSortedPositions = token.getIntArray("positions");
ComponentName[] extensions = (ComponentName[]) token.getParcelableArray("extensions");
for (int i = 0; i < reverseSortedPositions.length; i++) {
mSelectedExtensions.add(reverseSortedPositions[i], extensions[i]);
}
repopulateAvailableExtensions();
mSelectedExtensionsAdapter.notifyDataSetChanged();
}
public class ExtensionListAdapter extends BaseAdapter {
private static final int VIEW_TYPE_ITEM = 0;
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
return mSelectedExtensions.size();
}
@Override
public int getItemViewType(int position) {
return VIEW_TYPE_ITEM;
}
@Override
public Object getItem(int position) {
return mSelectedExtensions.get(position);
}
@Override
public long getItemId(int position) {
return mSelectedExtensions.get(position).hashCode();
}
@Override
public boolean isEnabled(int position) {
return false;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
switch (getItemViewType(position)) {
case VIEW_TYPE_ITEM: {
ComponentName cn = (ComponentName) getItem(position);
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.list_item_extension, parent, false);
}
TextView titleView = (TextView) convertView.findViewById(android.R.id.text1);
TextView descriptionView = (TextView) convertView
.findViewById(android.R.id.text2);
ImageView iconView = (ImageView) convertView.findViewById(android.R.id.icon1);
View overflowButton = convertView.findViewById(R.id.overflow_button);
final ExtensionListing listing = mExtensionListings.get(cn);
if (listing == null || TextUtils.isEmpty(listing.title())) {
iconView.setImageBitmap(null);
titleView.setText(cn.flattenToShortString());
descriptionView.setVisibility(View.GONE);
overflowButton.setVisibility(View.GONE);
} else {
iconView.setImageDrawable(mExtensionIcons.get(listing.componentName()));
titleView.setText(listing.title());
descriptionView.setVisibility(
TextUtils.isEmpty(listing.description()) ? View.GONE : View.VISIBLE);
descriptionView.setText(listing.description());
overflowButton.setVisibility(View.VISIBLE);
overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mUndoBarController.hideUndoBar(false);
PopupMenu menu = new PopupMenu(view.getContext(), view);
menu.inflate(R.menu.configure_item_overflow);
if (listing.settingsActivity() == null) {
menu.getMenu().findItem(R.id.action_settings).setVisible(false);
}
menu.setOnMenuItemClickListener(
new OverflowItemClickListener(position));
menu.show();
}
});
}
return convertView;
}
}
return null;
}
private class OverflowItemClickListener implements PopupMenu.OnMenuItemClickListener {
private int mPosition;
public OverflowItemClickListener(int position) {
mPosition = position;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (mPosition > getCount()) {
return false;
}
switch (item.getItemId()) {
case R.id.action_remove:
mSwipeDismissTouchListener.dismiss(mPosition);
return true;
case R.id.action_settings:
ComponentName cn = (ComponentName) getItem(mPosition);
ExtensionListing listing = mExtensionListings.get(cn);
if (listing == null) {
return false;
}
showExtensionSettings(getActivity(), listing);
return true;
}
return false;
}
}
}
public void showExtensionSettings(Context context, ExtensionListing listing){
if (listing.settingsActivity() == null) {
// Nothing to show
return;
}
// Start the proxy activity
Intent i = new Intent(context, ExtensionSettingActivityProxy.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra(DashClockService.EXTRA_COMPONENT_NAME, listing.componentName().flattenToString());
i.putExtra(ExtensionSettingActivityProxy.EXTRA_SETTINGS_ACTIVITY,
listing.settingsActivity().flattenToString());
startActivity(i);
}
public static class CantAddExtensionDialog extends DialogFragment {
private static final String ARG_EXTENSION_TITLE = "title";
private static final String ARG_EXTENSION_PACKAGE_NAME = "package_name";
public static CantAddExtensionDialog createInstance(String extensionPackageName,
String extensionTitle) {
CantAddExtensionDialog fragment = new CantAddExtensionDialog();
Bundle args = new Bundle();
args.putString(ARG_EXTENSION_TITLE, extensionTitle);
args.putString(ARG_EXTENSION_PACKAGE_NAME, extensionPackageName);
fragment.setArguments(args);
return fragment;
}
public CantAddExtensionDialog() {
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String extensionTitle = getArguments().getString(ARG_EXTENSION_TITLE);
final String extensionPackageName = getArguments().getString(ARG_EXTENSION_PACKAGE_NAME);
return new AlertDialog.Builder(getActivity(), R.style.Theme_Dialog)
.setTitle(R.string.incompatible_extension_title)
.setMessage(Html.fromHtml(getString(
R.string.incompatible_extension_message_search_play_template,
extensionTitle)))
.setPositiveButton(R.string.search_play,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Intent playIntent = new Intent(Intent.ACTION_VIEW);
playIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
playIntent.setData(Uri.parse(
"https://play.google.com/store/apps/details?id="
+ extensionPackageName));
try {
startActivity(playIntent);
} catch (ActivityNotFoundException ignored) {
}
dialog.dismiss();
}
}
)
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.dismiss();
}
}
)
.create();
}
}
}