package com.buzzbuddy.android; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Set; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import com.actionbarsherlock.app.SherlockListFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.emilsjolander.components.stickylistheaders.StickyListHeadersListView; public class BuzzBuddyFragment extends SherlockListFragment implements OnItemClickListener, OnDismissListener, OnCancelListener, Comparator<ResolveInfo>, OnClickListener, OnCheckedChangeListener { private static final String BUZZ_BUDDY_PACKAGE = BuzzBuddyFragment.class.getPackage().getName(); private static final String ACTIVITY_NAME = BuzzBuddyFragment.class.getSimpleName(); private static final String TAG = ACTIVITY_NAME; private static final String ACCESSIBILITY_SERVICE_NAME = BUZZ_BUDDY_PACKAGE + "/" + BUZZ_BUDDY_PACKAGE + "." + NotificationDetectorService.class.getSimpleName(); private BuzzDB base; private VibrationPatternDialog vibrationPatternDialog; private VibrationPattern vibrationPattern; private boolean isCanceled; private int listPosition; private List<ResolveInfo> unassignedApps, assignedApps, recommendedApps; private BuzzBuddyAdapter adapter; private StickyListHeadersListView stickyList; private CustomAlertDialog alertDialog; private boolean forceClear = false; @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_buzz_buddy, container, false); } @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); setMenuVisibility(false); } @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final BuzzBuddyApp app = (BuzzBuddyApp) getActivity().getApplication(); base = app.getDatabase(); stickyList = (StickyListHeadersListView) getListView(); stickyList.setOnItemClickListener(this); } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.menu_buzz_buddy, menu); if (BuildConfig.DEBUG) { Log.d(TAG, "onCreateOptionsMenu"); } } @Override public boolean onOptionsItemSelected(final MenuItem item) { // handle item selection switch (item.getItemId()) { case R.id.clear_selections: hideMenu(); clearChecks(); return true; case R.id.delete_selections: hideMenu(); final Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); vibrator.cancel(); adapter.enablePlaybackButtons(); deleteSelections(); return true; default: return super.onOptionsItemSelected(item); } } private void hideMenu() { setMenuVisibility(false); forceClear = adapter.getCheckedItemsSize() > 0; } private void clearChecks() { adapter.getCheckedItems().clear(); // why call notifyDataSetChanged? the data hasn't changed, but we're // asking for the list to be re-rendered anyways. Which means that the // check boxes will all be unchecked (and we make sure they're unchecked // when getView is called) adapter.notifyDataSetChanged(); } private void deleteSelections() { final List<ResolveInfo> toDelete = new LinkedList<ResolveInfo>(); final Set<Integer> checkedItems = adapter.getCheckedItems(); for (final Integer index : checkedItems) { toDelete.add(deleteFromRecordedApps(index)); } assignedApps.removeAll(toDelete); Collections.sort(unassignedApps, this); Collections.sort(recommendedApps, this); checkedItems.clear(); adapter.notifyDataSetChanged(); deleteAppsFromDatabase(toDelete); } private void deleteAppsFromDatabase(final List<ResolveInfo> toDelete) { new Thread(new Runnable() { @Override public void run() { for (final ResolveInfo elem : toDelete) { base.deleteByPackageName(elem.activityInfo.applicationInfo.packageName); } }; }).start(); } @Override public void onResume() { super.onResume(); new GetListItemsTask().execute(); // open accessibility menu checkAccessibility(); } @Override public void onStop() { super.onStop(); if (vibrationPattern != null && vibrationPatternDialog.isShowing()) { vibrationPatternDialog.dismiss(); } } @Override public void onDestroy() { if (BuildConfig.DEBUG) { Log.d(TAG, "onDestroy"); } super.onDestroy(); } private void checkAccessibility() { if (isAccessibilityEnabled()) { return; } if (alertDialog != null && alertDialog.isShowing()) { return; } if (alertDialog == null) { alertDialog = new CustomAlertDialog(getActivity(), R.style.VibrationPatternDialogStyle); alertDialog.setOnClickListener(new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int whichButton) { enableAccessabilitySettings(); alertDialog.dismiss(); } }); } alertDialog.show(); } private void enableAccessabilitySettings() { final Intent settingsIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else { settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); } startActivity(settingsIntent); } private boolean isAccessibilityEnabled() { int accessibilityEnabled = 0; try { accessibilityEnabled = Settings.Secure.getInt(getActivity().getContentResolver(), android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); } catch (final SettingNotFoundException e) { Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage()); } if (accessibilityEnabled == 0) { return false; } final String settingValue = Settings.Secure.getString(getActivity().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); if (settingValue != null) { final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':'); splitter.setString(settingValue); while (splitter.hasNext()) { final String accessabilityService = splitter.next(); if (accessabilityService.equalsIgnoreCase(ACCESSIBILITY_SERVICE_NAME)) { return true; } } } return false; } @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { final Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); vibrator.cancel(); hideMenu(); clearChecks(); adapter.enablePlaybackButtons(); Log.i(TAG, "onItemClick, postion = " + position); listPosition = position; if (vibrationPatternDialog == null) { vibrationPatternDialog = new VibrationPatternDialog(getActivity(), R.style.VibrationPatternDialogStyle); vibrationPatternDialog.setOnDismissListener(this); vibrationPatternDialog.setOnCancelListener(this); } vibrationPattern = new VibrationPattern(); vibrationPatternDialog.setVibrationPattern(vibrationPattern); vibrationPatternDialog.setCurrentApp((ResolveInfo) adapter.getItem(position)); isCanceled = false; vibrationPatternDialog.show(); } @Override public void onDismiss(final DialogInterface dialog) { if (!isCanceled) { final Long[] finalPattern = vibrationPattern.getFinalizedPattern(); if (finalPattern == null) { return; } final ContentValues values = new ContentValues(); final String patternString = serializePattern(finalPattern); final String appName = getAppNameForPosition(listPosition); Log.i(TAG, "patternString = " + patternString); Log.i(TAG, "appName = " + appName); values.put(BuzzDB.APP_KEY_NAME, appName); values.put(BuzzDB.APP_KEY_VIBRATION, patternString); values.put(BuzzDB.APP_KEY_DATE, Calendar.getInstance().getTimeInMillis()); final Cursor nameCheck = base.queryByPackageName(appName); nameCheck.moveToFirst(); if (nameCheck.getCount() > 0) { final long rowId = nameCheck.getLong(BuzzDB.INDEX_ROW_ID); updateOrAddToRecordedApps(listPosition, true); new Thread(new Runnable() { @Override public void run() { base.updateRow(BuzzDB.DATABASE_APP_TABLE, rowId, values); } }).start(); } else { updateOrAddToRecordedApps(listPosition, false); new Thread(new Runnable() { @Override public void run() { base.createRow(BuzzDB.DATABASE_APP_TABLE, values); } }).start(); } nameCheck.close(); } } private void updateOrAddToRecordedApps(final int position, final boolean update) { // if adding new if (!update) { // if you are adding a recommended app if (position < assignedApps.size() + recommendedApps.size()) { assignedApps.add(0, recommendedApps.remove(position - assignedApps.size())); } else { assignedApps.add(0, unassignedApps.remove(position - assignedApps.size() - recommendedApps.size())); } } else { assignedApps.add(0, assignedApps.remove(position)); } adapter.notifyDataSetChanged(); } private ResolveInfo deleteFromRecordedApps(final int position) { final ResolveInfo removed = assignedApps.get(position); final String packageName = removed.activityInfo.applicationInfo.packageName; Log.d(TAG, "deleting package: " + packageName); if (BuzzBuddyApp.recommendedPackages.contains(packageName)) { recommendedApps.add(removed); return removed; } unassignedApps.add(removed); return removed; } private String getAppNameForPosition(final int position) { if (position < assignedApps.size()) { return assignedApps.get(position).activityInfo.applicationInfo.packageName; } else if (position < assignedApps.size() + recommendedApps.size()) { return recommendedApps.get(position - assignedApps.size()).activityInfo.applicationInfo.packageName; } else { return unassignedApps.get(position - assignedApps.size() - recommendedApps.size()).activityInfo.packageName; } } private static String serializePattern(final Long[] finalPattern) { String toReturn = "" + finalPattern[0]; for (int i = 1; i < finalPattern.length; ++i) { toReturn += "-" + finalPattern[i]; } return toReturn; } @Override public void onCancel(final DialogInterface dialog) { Log.i(TAG, "dialog canceled"); isCanceled = true; } @Override public int compare(final ResolveInfo first, final ResolveInfo second) { final PackageManager pm = getActivity().getPackageManager(); final String firstLabel = (String) first.loadLabel(pm); final String secondLabel = (String) second.loadLabel(pm); return firstLabel.compareToIgnoreCase(secondLabel); } @Override public void onClick(final View v) { if (v.isSelected()) { // stop button showing Log.i(TAG, "stop playback clicked, position = " + v.getTag()); final Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); vibrator.cancel(); adapter.enablePlaybackButtons(); return; } // play button showing Log.i(TAG, "playback clicked, position = " + v.getTag()); final Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); final int index = (Integer) v.getTag(); String patternString = "0"; final ResolveInfo item = assignedApps.get(index); final String pName = item.activityInfo.applicationInfo.packageName; final Cursor entry = base.queryByPackageName(pName); entry.moveToFirst(); if (entry.getCount() > 0) { patternString = entry.getString(BuzzDB.APP_INDEX_VIBRATION); } final long[] vibrationPattern = VibrationPatternUtils.deserializePattern(patternString); final long delay = totalPatternTime(vibrationPattern); adapter.disableOtherPlaybackButtonsForTime(index, delay); vibrator.vibrate(vibrationPattern, -1); entry.close(); } private static long totalPatternTime(final long[] pattern) { long toReturn = 0; for (final long elem : pattern) { toReturn += elem; } return toReturn; } @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { final Set<Integer> checked = adapter.getCheckedItems(); if (isChecked) { Log.i(TAG, "checkbox checked, position = " + buttonView.getTag()); if (checked.size() == 0) { setMenuVisibility(true); } checked.add((Integer) buttonView.getTag()); } else { Log.i(TAG, "checkbox unchecked, position = " + buttonView.getTag()); if (checked.size() == 1) { if (!forceClear) { setMenuVisibility(false); } } forceClear = false; checked.remove(buttonView.getTag()); } } private class GetListItemsTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(final Void... unused) { final BuzzBuddyApp app = (BuzzBuddyApp) BuzzBuddyFragment.this.getActivity().getApplication(); unassignedApps = app.getUnassignedApps(); assignedApps = app.getAssignedApps(); recommendedApps = app.getRecommendedApps(); return (null); } @Override protected void onPostExecute(final Void unused) { adapter = new BuzzBuddyAdapter(BuzzBuddyFragment.this.getActivity(), BuzzBuddyFragment.this, assignedApps, unassignedApps, recommendedApps); stickyList.setAdapter(adapter); } } }