Java tutorial
/* * Kontalk Android client * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org> * 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.kontalk.ui; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.support.v4.app.ListFragment; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.SwipeRefreshLayout; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import lb.library.PinnedHeaderListView; import org.kontalk.R; import org.kontalk.data.Contact; import org.kontalk.message.TextComponent; import org.kontalk.sync.SyncAdapter; import org.kontalk.ui.adapter.ContactsListAdapter; import org.kontalk.ui.view.ContactPickerListener; import org.kontalk.ui.view.ContactsListItem; import org.kontalk.util.RunnableBroadcastReceiver; import org.kontalk.util.SystemUtils; /** Contacts list selection fragment. */ public class ContactsListFragment extends ListFragment implements ContactsListAdapter.OnContentChangedListener, ContactsSyncer { private Cursor mCursor; private ContactsListAdapter mListAdapter; private SwipeRefreshLayout mRefresher; private LocalBroadcastManager mBroadcastManager; private RunnableBroadcastReceiver mSyncMonitor; private Handler mHandler; private MenuItem mSyncButton; private boolean mMultiselect; private final RunnableBroadcastReceiver.ActionRunnable mPostSyncAction = new RunnableBroadcastReceiver.ActionRunnable() { public void run(String action) { if (SyncAdapter.ACTION_SYNC_START.equals(action)) { setSyncing(true); } else if (SyncAdapter.ACTION_SYNC_FINISH.equals(action)) { startQuery(); setSyncing(false); } } }; public static ContactsListFragment newInstance(boolean multiselect) { ContactsListFragment f = new ContactsListFragment(); Bundle args = new Bundle(); args.putBoolean("multiselect", multiselect); f.setArguments(args); return f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); if (savedInstanceState == null) { mMultiselect = getArguments().getBoolean("multiselect", false); } else { mMultiselect = savedInstanceState.getBoolean("multiselect", false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("multiselect", mMultiselect); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.contacts_list, container, false); } // lint bug: for setChoiceMode which is actually available in API level 9 @SuppressLint("NewApi") @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); PinnedHeaderListView list = (PinnedHeaderListView) getListView(); View pinnedHeaderView = LayoutInflater.from(view.getContext()) .inflate(R.layout.pinned_header_listview_side_header, list, false); list.setPinnedHeaderView(pinnedHeaderView); list.setEmptyView(view.findViewById(android.R.id.empty)); list.setChoiceMode(mMultiselect ? ListView.CHOICE_MODE_MULTIPLE : ListView.CHOICE_MODE_NONE); mRefresher = (SwipeRefreshLayout) view.findViewById(R.id.refresher); mRefresher.setEnabled(false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Activity parent = getActivity(); mListAdapter = new ContactsListAdapter(parent, getListView()); mListAdapter.setPinnedHeader(parent); PinnedHeaderListView list = (PinnedHeaderListView) getListView(); list.setEnableHeaderTransparencyChanges(false); list.setOnScrollListener(mListAdapter); mListAdapter.setOnContentChangedListener(this); setListAdapter(mListAdapter); mHandler = new Handler(); mBroadcastManager = LocalBroadcastManager.getInstance(parent); // retain current sync state to hide the refresh button and start indeterminate progress registerSyncReceiver(); if (SyncAdapter.isActive(parent)) { setSyncing(true); } } @Override public void onStop() { super.onStop(); mListAdapter.changeCursor(null); try { // make sure the cursor is really closed mCursor.close(); } catch (Exception e) { // ignored } // cancel sync monitor if (mSyncMonitor != null) { mBroadcastManager.unregisterReceiver(mSyncMonitor); mSyncMonitor = null; } } @Override public void onResume() { super.onResume(); startQuery(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.contacts_list_menu, menu); mSyncButton = menu.findItem(R.id.menu_refresh); Context ctx = getActivity(); if (ctx != null) mSyncButton.setVisible(!SyncAdapter.isActive(getActivity()) && !mMultiselect); // multiselect mode needs the confirm action menu.findItem(R.id.menu_compose).setVisible(mMultiselect); menu.findItem(R.id.menu_invite).setVisible(!mMultiselect); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_compose: // using clone because listview returns its original copy openSelectedContacts(SystemUtils.cloneSparseBooleanArray(getListView().getCheckedItemPositions())); return true; case R.id.menu_refresh: startSync(true); return true; case R.id.menu_invite: startInvite(); return true; } return super.onOptionsItemSelected(item); } private void openSelectedContacts(SparseBooleanArray checked) { List<Contact> selected = new ArrayList<>(checked.size()); for (int i = 0, c = mListAdapter.getCount(); i < c; ++i) { if (checked.get(i)) { Cursor cursor = (Cursor) mListAdapter.getItem(i); Contact contact = Contact.fromUsersCursor(getContext(), cursor); selected.add(contact); } } ContactPickerListener parent = (ContactPickerListener) getActivity(); if (parent != null) { parent.onContactsSelected(this, selected); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { if (!mMultiselect) { ContactPickerListener parent = (ContactPickerListener) getActivity(); if (parent != null) { parent.onContactSelected(this, ((ContactsListItem) v).getContact()); } } } @Override public void startSync(boolean errorWarning) { Activity activity = getActivity(); if (SystemUtils.isNetworkConnectionAvailable(activity)) { if (SyncAdapter.requestSync(activity, true)) setSyncing(true); } else if (errorWarning) { Toast.makeText(activity, R.string.err_sync_nonetwork, Toast.LENGTH_LONG).show(); } } @Override public void setSyncing(boolean syncing) { mRefresher.setRefreshing(syncing); if (mSyncButton != null) mSyncButton.setVisible(!syncing && !mMultiselect); } private void registerSyncReceiver() { // register sync monitor if (mSyncMonitor == null) { mSyncMonitor = new RunnableBroadcastReceiver(mPostSyncAction, mHandler); IntentFilter filter = new IntentFilter(SyncAdapter.ACTION_SYNC_FINISH); filter.addAction(SyncAdapter.ACTION_SYNC_START); mBroadcastManager.registerReceiver(mSyncMonitor, filter); } } public void startQuery() { final Context context = getContext(); if (context != null) { mCursor = Contact.queryContacts(context); mListAdapter.changeCursor(mCursor); } } @Override public void onContentChanged(ContactsListAdapter adapter) { startQuery(); } private void startInvite() { Context ctx = getActivity(); Intent shareIntent = SystemUtils.externalIntent(Intent.ACTION_SEND); shareIntent.setType(TextComponent.MIME_TYPE); shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.text_invite_message)); List<ResolveInfo> resInfo = ctx.getPackageManager().queryIntentActivities(shareIntent, 0); // having size=1 means that we are the only handlers if (resInfo != null && resInfo.size() > 1) { List<Intent> targets = new ArrayList<>(); for (ResolveInfo resolveInfo : resInfo) { String packageName = resolveInfo.activityInfo.packageName; if (!ctx.getPackageName().equals(packageName)) { // copy intent and add resolved info Intent targetShareIntent = new Intent(shareIntent); targetShareIntent.setPackage(packageName) .setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name)) .putExtra("org.kontalk.invite.label", resolveInfo.activityInfo.loadLabel(ctx.getPackageManager())); targets.add(targetShareIntent); } } if (targets.size() > 0) { // initial intents are added before the main intent, so we remove the last one here Intent chooser = Intent.createChooser(targets.remove(targets.size() - 1), getString(R.string.menu_invite)); if (targets.size() > 0) { Collections.sort(targets, new DisplayNameComparator()); // remove custom extras for (Intent intent : targets) intent.removeExtra("org.kontalk.invite.label"); Parcelable[] extraIntents = targets.toArray(new Parcelable[targets.size()]); chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents); } startActivity(chooser); return; } } // no activity to handle invitation Toast.makeText(ctx, R.string.warn_invite_no_app, Toast.LENGTH_SHORT).show(); } static class DisplayNameComparator implements Comparator<Intent> { DisplayNameComparator() { mCollator.setStrength(Collator.PRIMARY); } public final int compare(Intent a, Intent b) { CharSequence sa = a.getCharSequenceExtra("org.kontalk.invite.label"); if (sa == null) sa = a.getComponent().getClassName(); CharSequence sb = b.getCharSequenceExtra("org.kontalk.invite.label"); if (sb == null) sb = b.getComponent().getClassName(); return mCollator.compare(sa.toString(), sb.toString()); } private final Collator mCollator = Collator.getInstance(); } }