/*
* Copyright (C) 2013 Fairphone Project
*
* 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 org.fairphone.launcher.edgeswipe.edit;
import com.flurry.android.FlurryAgent;
import org.fairphone.launcher.ApplicationInfo;
import org.fairphone.launcher.R;
import org.fairphone.launcher.edgeswipe.ui.EditFavoritesGridView;
import org.fairphone.launcher.edgeswipe.ui.EditFavoritesGridView.OnEditFavouritesIconDraggedListener;
import org.fairphone.launcher.util.FlurryHelper;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
/**
* Edit favorites activity implements functionality to edit your favorite apps
* that will appear with the edge swipe.
*/
public class EditFavoritesActivity extends Activity implements
View.OnDragListener, DragDropItemLayoutListener {
private static final String TAG = EditFavoritesActivity.class
.getSimpleName();
// This is used to differentiate a drag from the all apps to favorites
// from a drag between two favorites to perform a swap
public static final int SELECTED_APPS_DRAG = 0;
public static final int ALL_APPS_DRAG = 1;
private AllAppsListAdapter mAllAppsListAdapter;
private ArrayList<ApplicationInfo> mAllApps;
private ArrayList<RelativeLayout> mFavIcons;
private ApplicationInfo[] mSelectedApps;
private EditFavoritesGridView mAllAppsGridView;
private int mDragOrigin;
@Override
protected void onStart() {
super.onStart();
FlurryHelper.startFlurrySession(this);
}
@Override
protected void onStop() {
super.onStop();
FlurryHelper.endFlurrySession(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fp_edit_favorites);
mAllApps = AppDiscoverer.getInstance().getPackages();
mSelectedApps = FavoritesStorageHelper.loadSelectedApps(this, 4);
mFavIcons = new ArrayList<RelativeLayout>();
mFavIcons.add((RelativeLayout) findViewById(R.id.favouriteGroup1));
mFavIcons.add((RelativeLayout) findViewById(R.id.favouriteGroup2));
mFavIcons.add((RelativeLayout) findViewById(R.id.favouriteGroup3));
mFavIcons.add((RelativeLayout) findViewById(R.id.favouriteGroup4));
setupAllAppsList();
setupSelectedAppsList();
}
@Override
public void onPause() {
super.onPause();
}
/**
* Setup the list with all the apps installed on the device.
*/
private void setupAllAppsList() {
mAllAppsGridView = (EditFavoritesGridView) findViewById(R.id.allAppsGridView);
mAllAppsListAdapter = new AllAppsListAdapter(this);
mAllAppsListAdapter.setAllApps(mAllApps);
mAllAppsGridView.setLongClickable(true);
mAllAppsGridView
.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent,
View v, int position, long id) {
startDraggingIcon(v, position);
return true;
}
});
mAllAppsGridView
.setOnEditFavouritesIconDraggedListener(new OnEditFavouritesIconDraggedListener() {
@Override
public void OnEditFavouritesIconDragged(
AdapterView<?> parent, View view, int position,
long id) {
startDraggingIcon(view, position);
}
});
mAllAppsGridView.setAdapter(mAllAppsListAdapter);
// set the drag listener to enable favorite icon removal
View allAppsGroupView = findViewById(R.id.allAppsGroup);
allAppsGroupView.setOnDragListener(new DropDragEventListener(this,
mFavIcons, mSelectedApps, mAllApps, true));
}
private void startDraggingIcon(View view, int position) {
// display a circle around the possible destinations
toggleFavoriteCircleSelection(-1, true);
View mainView = this.getWindow().getDecorView();
ApplicationInfo applicationInfo = mAllApps.get(position);
// set the item with the origin of the drag and the index of the dragged
// view
mDragOrigin = EditFavoritesActivity.ALL_APPS_DRAG;
String selectedItem = serializeItem(mDragOrigin, position);
ClipData.Item item = new ClipData.Item(selectedItem);
ClipData dragData = ClipData.newPlainText(
applicationInfo.getApplicationTitle(),
applicationInfo.getApplicationTitle());
dragData.addItem(item);
mainView.startDrag(dragData,
new IconDragShadowBuilder(view, new BitmapDrawable(
getResources(), applicationInfo.iconBitmap)), view, 0);
mainView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
/**
* Setup the list with the selected app list, which is the holder of the
* apps that has been selected to become the favorite apps.
*/
private void setupSelectedAppsList() {
for (int i = 0; i < 4; i++) {
setupFavoriteIcon(mFavIcons.get(i), mSelectedApps[i], i, false);
// set the listeners
// The last argument is set to false since this hasn't the ability
// to remove icons, it only switches them
mFavIcons.get(i).setOnDragListener(
new DropDragEventListener(this, mFavIcons, mSelectedApps,
mAllApps, false));
}
}
@Override
public void setupFavoriteIcon(RelativeLayout rla,
ApplicationInfo applicationInfo, int idx, boolean performAnimation) {
if (applicationInfo == null) {
FlurryAgent.logEvent(FlurryHelper.EDIT_FAVORITES_REMOVE_ICON);
final View dragPlaceholderView = rla.getChildAt(0);
final View iconView = rla.getChildAt(1);
if (performAnimation) {
startViewFadeOutFadeInAnimation(dragPlaceholderView, iconView,
null);
} else {
dragPlaceholderView.setVisibility(View.VISIBLE);
iconView.setVisibility(View.INVISIBLE);
}
rla.setOnLongClickListener(null);
mSelectedApps[idx] = null;
} else {
FlurryAgent.logEvent(FlurryHelper.EDIT_FAVORITES_ADD_ICON);
final View dragPlaceholderView = rla.getChildAt(0);
final TextView iconView = (TextView) rla.getChildAt(1);
// Log.d(TAG, "Adding app : " + applicationInfo.getApplicationTitle());
if (mSelectedApps[idx] == null) {
updateFavoriteIcon(applicationInfo, iconView);
if (performAnimation) {
startViewFadeOutFadeInAnimation(iconView,
dragPlaceholderView, null);
} else {
dragPlaceholderView.setVisibility(View.INVISIBLE);
iconView.setVisibility(View.VISIBLE);
}
} else {
if (performAnimation) {
startViewFadeOutFadeInAnimation(null, iconView,
applicationInfo);
} else {
updateFavoriteIcon(applicationInfo, iconView);
dragPlaceholderView.setVisibility(View.INVISIBLE);
iconView.setVisibility(View.VISIBLE);
}
}
mSelectedApps[idx] = applicationInfo;
// Set the listener
// pass the main view and the instance setup the drag and visibility
// of some views
final View mainView = this.getWindow().getDecorView();
rla.setOnLongClickListener(new IdLongClickListener(idx, mainView,
this));
}
FavoritesStorageHelper.storeSelectedApps(this, mSelectedApps);
}
/**
* Update the icon and label of a favorite
*
* @param applicationInfo
* App information that contains the icon and label
* @param icon
* the icon to update
*/
private void updateFavoriteIcon(ApplicationInfo applicationInfo,
final TextView icon) {
Drawable drawable = new BitmapDrawable(getResources(),
applicationInfo.iconBitmap);
Resources r = getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
r.getDisplayMetrics());
drawable.setBounds(0, 0, Math.round(px), Math.round(px));
icon.setCompoundDrawables(null, drawable, null, null);
icon.setText(applicationInfo.getApplicationTitle());
}
/**
* Performs the animation when replacing one favorite
*
* @param viewToFadeIn
* view that will appear. When null it means that we are swapping
* two favorites
* @param viewToFadeOut
* view that will disappear.
* @param applicationInfo
* app info that is used to swap two favorites
*/
private void startViewFadeOutFadeInAnimation(final View viewToFadeIn,
final View viewToFadeOut, final ApplicationInfo applicationInfo) {
Animation fadeOutAnimation = AnimationUtils.loadAnimation(this,
R.anim.fade_out_fast);
fadeOutAnimation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
viewToFadeOut.setVisibility(View.INVISIBLE);
if (applicationInfo == null && viewToFadeIn != null) {
if (viewToFadeIn.getVisibility() != View.VISIBLE) {
viewToFadeIn.setVisibility(View.VISIBLE);
Animation fadeInAnimation = AnimationUtils
.loadAnimation(getApplicationContext(),
R.anim.fade_in_fast);
viewToFadeIn.startAnimation(fadeInAnimation);
}
} else if (applicationInfo != null) {
// get the new icon
updateFavoriteIcon(applicationInfo,
(TextView) viewToFadeOut);
viewToFadeOut.setVisibility(View.VISIBLE);
Animation fadeInAnimation = AnimationUtils.loadAnimation(
getApplicationContext(), R.anim.fade_in_fast);
viewToFadeOut.startAnimation(fadeInAnimation);
}
}
});
if (viewToFadeOut.getVisibility() == View.VISIBLE) {
viewToFadeOut.startAnimation(fadeOutAnimation);
}
}
/**
* Capture the back button press, to make sure we save the selected apps
* before exiting.
*/
@Override
public void onBackPressed() {
FavoritesStorageHelper.storeSelectedApps(this, mSelectedApps);
Intent intent = getIntent();
setResult(RESULT_OK, intent);
finish();
}
/**
* Serializes the item id and origin that is being dragged.
*
* @param appOrigin
* the origin: can be SELECT_APPS_DRAG or ALL_APPS_DRAG
* @param appIndex
* the item index
* @return the serialized item info
*/
public static String serializeItem(int appOrigin, int appIndex) {
String selectedItem = appOrigin + ";" + appIndex;
return selectedItem;
}
/**
* Deserializes the item id and origin that is being dragged.
*
* @param toDeserialize
* string to deserialize
* @return an array containing {appOrign, appIndex}
*/
public static String[] deserializeItem(String toDeserialize) {
String[] selectedItem = toDeserialize.split(";");
return selectedItem;
}
class IdLongClickListener implements View.OnLongClickListener {
private int mId;
private View mMainView;
private DragDropItemLayoutListener mListener;
public IdLongClickListener(int id, View mainView,
DragDropItemLayoutListener listener) {
super();
mId = id;
mMainView = mainView;
mListener = listener;
}
@Override
public boolean onLongClick(View v) {
// Show the zone where favorites can be removed
mListener.showAllAppsRemoveZone();
// display a circle around the possible destinations
toggleFavoriteCircleSelection(-1, true);
// set the drag info
ApplicationInfo applicationInfo = mSelectedApps[mId];
// set the item with the origin of the drag and the index of the
// dragged view
mDragOrigin = EditFavoritesActivity.SELECTED_APPS_DRAG;
String selectedItem = serializeItem(mDragOrigin, mId);
ClipData.Item item = new ClipData.Item(selectedItem);
ClipData dragData = ClipData.newPlainText(
applicationInfo.getApplicationTitle(),
applicationInfo.getApplicationTitle());
dragData.addItem(item);
mMainView.startDrag(dragData, new IconDragShadowBuilder(v,
new BitmapDrawable(getResources(),
applicationInfo.iconBitmap)), v, 0);
mMainView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
}
@Override
public boolean onDrag(View v, DragEvent event) {
return false;
}
private static class DropDragEventListener implements View.OnDragListener {
private ArrayList<RelativeLayout> mFavIcons;
private ApplicationInfo[] mSelectedApps;
private ArrayList<ApplicationInfo> mAllApps;
private DragDropItemLayoutListener mListener;
/**
* True means that a favorite will be removed from the list.
*/
private boolean mIsToRemove;
public DropDragEventListener(DragDropItemLayoutListener listener,
ArrayList<RelativeLayout> favIcons,
ApplicationInfo[] mSelectedApps2,
ArrayList<ApplicationInfo> allApps,
boolean toDeleteFromFavorites) {
mFavIcons = favIcons;
mSelectedApps = mSelectedApps2;
mAllApps = allApps;
mListener = listener;
mIsToRemove = toDeleteFromFavorites;
}
@Override
public boolean onDrag(View v, DragEvent event) {
// Defines a variable to store the action type for the incoming
// event
final int action = event.getAction();
RelativeLayout rla = (RelativeLayout) v;
// Handles each of the expected events
switch (action) {
case DragEvent.ACTION_DRAG_STARTED: {
// Moving an icon to an occupied position replaces the
// current one
return true;
}
case DragEvent.ACTION_DRAG_ENTERED:
// toggle the red glow when removing favorites
int idx = mFavIcons.indexOf(rla);
if (idx == -1) {
mListener.toggleAllAppRemoveZoneRedGlow(event.getX(),
event.getY());
} else {
mListener.hideAllAppsRemoveZoneRedGlow();
}
break;
case DragEvent.ACTION_DRAG_EXITED:
mListener.hideAllAppsRemoveZoneRedGlow();
break;
case DragEvent.ACTION_DROP:
int id = mFavIcons.indexOf(rla);
// get the Item data
// 0 is the origin
// 1 is the index
String[] clipItemData = EditFavoritesActivity
.deserializeItem(event
.getClipData()
.getItemAt(
event.getClipData().getItemCount() - 1)
.getText().toString());
int position = -1;
ApplicationInfo info = null;
// obtain the applicationInfo
switch (Integer.parseInt(clipItemData[0])) {
case EditFavoritesActivity.SELECTED_APPS_DRAG:
position = Integer.parseInt(clipItemData[1]);
// when not removing an icon swap is performed
if (!mIsToRemove) {
info = mSelectedApps[position];
FlurryAgent.logEvent(FlurryHelper.EDIT_FAVORITES_SWAP_ICONS);
mListener.setupFavoriteIcon(mFavIcons.get(position),
mSelectedApps[id], position, true);
} else {
// remove the favorite
id = position;
rla = mFavIcons.get(id);
}
break;
case EditFavoritesActivity.ALL_APPS_DRAG:
position = Integer.parseInt(clipItemData[1]);
info = mAllApps.get(position);
break;
default:
Log.e(TAG,
"Unknown Icon Origin received by OnDragListener.");
break;
}
// only setup the icon if a valid id is obtained
if (id != -1) {
mListener.setupFavoriteIcon(rla, info, id, true);
}
return true;
case DragEvent.ACTION_DRAG_ENDED:
// hide zone remove zone
mListener.hideAllAppsRemoveZone();
return true;
default:
Log.e(TAG, "Unknown action type received by OnDragListener.");
}
return false;
}
}
private static class IconDragShadowBuilder extends View.DragShadowBuilder {
private Drawable mIcon;
public IconDragShadowBuilder(View v, Drawable icon) {
super(v);
// Creates a draggable image that will fill the Canvas provided by
// the system.
// shadow = new ColorDrawable(Color.LTGRAY);
mIcon = icon;
}
@Override
public void onProvideShadowMetrics(Point size, Point touch) {
// Defines local variables
int width;
int height;
// Sets the width of the shadow to half the width of the original
// View
width = 100;
// Sets the height of the shadow to half the height of the original
// View
height = 100;
// The drag shadow is a ColorDrawable. This sets its dimensions to
// be the same as the
// Canvas that the system will provide. As a result, the drag shadow
// will fill the
// Canvas.
mIcon.setBounds(0, 0, width, height);
// Sets the size parameter's width and height values. These get back
// to the system
// through the size parameter.
size.set(width, height);
// Sets the touch point's position to be in the middle of the drag
// shadow
touch.set(width / 2, height);
}
// Defines a callback that draws the drag shadow in a Canvas that the
// system constructs
// from the dimensions passed in onProvideShadowMetrics().
@Override
public void onDrawShadow(Canvas canvas) {
// Draws the ColorDrawable in the Canvas passed in from the system.
// shadow.draw(canvas);
mIcon.draw(canvas);
}
}
@Override
public void hideAllAppsRemoveZone() {
hideAllAppsRemoveZoneRedGlow();
View removeZoneView = findViewById(R.id.allAppsTextView);
removeZoneView.setVisibility(View.INVISIBLE);
mAllAppsGridView.setAlpha(1f);
// hide the circle around the possible destinations
toggleFavoriteCircleSelection(-1, false);
}
@Override
public void showAllAppsRemoveZone() {
View removeZoneView = findViewById(R.id.allAppsTextView);
removeZoneView.setVisibility(View.VISIBLE);
mAllAppsGridView.setAlpha(0.2f);
}
@Override
public void showAllAppsRemoveZoneRedGlow() {
View allAppsGroupView = findViewById(R.id.allAppsGroup);
allAppsGroupView.setBackground(getResources().getDrawable(R.drawable.fp_edit_favs_remove_red_bg));
}
@Override
public void hideAllAppsRemoveZoneRedGlow() {
View allAppsGroupView = findViewById(R.id.allAppsGroup);
allAppsGroupView.setBackground(getResources().getDrawable(
R.drawable.edit_menu_list_background));
}
@Override
public void toggleAllAppRemoveZoneRedGlow(float pointerX, float pointerY) {
// the red glow only makes sense when removing favorites
if (mDragOrigin == SELECTED_APPS_DRAG) {
if (isInRemoveZone(pointerX, pointerY)) {
showAllAppsRemoveZoneRedGlow();
} else {
hideAllAppsRemoveZoneRedGlow();
}
}
}
private boolean isInRemoveZone(float pointerX, float pointerY) {
boolean validX = false;
boolean validY = false;
View allAppsGroupView = findViewById(R.id.allAppsGroup);
validX = pointerX <= (allAppsGroupView.getX() + allAppsGroupView
.getWidth());
validY = pointerY >= allAppsGroupView.getY();
return validX && validY;
}
/**
* Display a circle around the favorite possible positions when configuring
* it
*
* @param selectedFavorite
* the favorite position that will not be circled. -1 means that
* all circles will be shown.
* @param showCircle
* true: displays a white circle; false: displays a faded white
* circle
*/
private void toggleFavoriteCircleSelection(int selectedFavorite,
boolean showCircle) {
for (int i = 0; i < mFavIcons.size(); i++) {
if (i != selectedFavorite && showCircle) {
mFavIcons.get(i)
.setBackground(
getResources().getDrawable(
R.drawable.edit_menu_circle));
} else {
mFavIcons.get(i).setBackground(
getResources().getDrawable(
R.drawable.edit_menu_circle_faded));
}
}
}
}