com.appsimobile.appsii.module.search.SearchController.java Source code

Java tutorial

Introduction

Here is the source code for com.appsimobile.appsii.module.search.SearchController.java

Source

/*
 * 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.search;

import android.content.ComponentName;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.speech.RecognitionListener;
import android.speech.SpeechRecognizer;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.appsimobile.appsii.AnalyticsManager;
import com.appsimobile.appsii.BuildConfig;
import com.appsimobile.appsii.LoaderManager;
import com.appsimobile.appsii.PageController;
import com.appsimobile.appsii.R;
import com.appsimobile.appsii.compat.LauncherAppsCompat;
import com.appsimobile.appsii.compat.UserHandleCompat;
import com.appsimobile.appsii.dagger.AppsiInjector;
import com.appsimobile.appsii.module.BaseContactInfo;
import com.appsimobile.appsii.module.PeopleQuery;
import com.appsimobile.appsii.module.apps.AppEntry;
import com.appsimobile.appsii.module.apps.AppHistoryUtils;
import com.appsimobile.appsii.module.people.ContactView;
import com.appsimobile.appsii.preference.PreferenceHelper;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

/**
 * Created by nick on 25/05/14.
 */
public class SearchController extends PageController implements AdapterView.OnItemClickListener,
        OnAppClickedListener, OnPersonClickedListener, ContactView.PeopleActionListener {

    static final int APPS_SEARCH_LOADER = 334001;

    static final int PEOPLE_SEARCH_LOADER = 334002;

    static final int RECENT_SEARCHES_LOADER = 334003;

    /**
     * The recycler-view containing the agenda-items
     */
    RecyclerView mRecyclerView;

    /**
     * The view where the user can enter the search query
     */
    EditText mSearchView;

    /**
     * The speech recognized used for speech search
     */
    SpeechRecognizer mSpeechRecognizer;

    /**
     * The query the user entered
     */
    String mQuery;

    /**
     * The search adapter. This shows the search results
     */
    SearchAdapter mSearchAdapter;

    /**
     * Loader callbacks for the people loader
     */
    PeopleCallbacks mPeopleCallbacks;

    /**
     * Loader callbacks for the apps loader
     */
    AppsCallbacks mAppsCallbacks;

    /**
     * Loader callbacks for the recent searches loader
     */
    RecentSearchedCallbacks mRecentSearchedCallbacks;

    /**
     * The shared-preferences we can get the configuration from
     */
    SharedPreferences mPreferences;

    RecyclerView mSearchRecyclerView;

    View mSearchDivider;

    GridLayoutManager mGridLayoutManager;

    SearchSuggestionsAdapter mSearchSuggestionsAdapter;

    View mSuggestionsAnchor;

    @Inject
    PreferenceHelper mPreferenceHelper;

    @Inject
    InputMethodManager mInputMethodManager;

    @Inject
    LauncherAppsCompat mLauncherAppsCompat;

    public SearchController(Context context, String title) {
        super(context, title);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.page_search, container, false);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSpeechRecognizer.destroy();
    }

    @Override
    protected void onViewDestroyed(View view) {

    }

    //    @Override
    //    public int getFlags() {
    //        return AbstractSidebarPagerAdapter.FLAG_NO_DECORATIONS;
    //    }

    @Override
    protected void onViewCreated(View view, Bundle savedInstanceState) {
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler);
        mSearchView = (EditText) view.findViewById(R.id.search_view);
        mSuggestionsAnchor = view.findViewById(R.id.search_wrapper);
        mSearchRecyclerView = (RecyclerView) view.findViewById(R.id.search_recycler);
        mSearchDivider = view.findViewById(R.id.search_divider);

        mSearchRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(getContext()));
        mSearchRecyclerView.setAdapter(mSearchSuggestionsAdapter);

        mRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(getContext()));
        mRecyclerView.setAdapter(mSearchAdapter);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        //        mRecyclerView.setOnScrollListener(new ToolbarScrollListener(this, mToolbar));

        Resources res = getResources();
        float density = res.getDisplayMetrics().density;

        // calculate the width of the sidebar
        int contentWidth = getContentWidth();
        if (contentWidth == 0) {
            float pct = mPreferenceHelper.getSidebarWidth() / 100f;
            contentWidth = (int) (res.getConfiguration().smallestScreenWidthDp * density * pct);
        }

        int dp104 = (int) (density * 104);
        int cellCount = contentWidth / dp104;
        if (cellCount < 3)
            cellCount = 3;
        if (cellCount > 3)
            cellCount--;

        mGridLayoutManager = new GridLayoutManager(getContext(), cellCount);
        mSearchAdapter.setSpanCount(cellCount);
        mRecyclerView.setLayoutManager(mGridLayoutManager);
        mGridLayoutManager.setSpanSizeLookup(mSearchAdapter.mSpanSizeLookup);

        mSearchView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                onSearchTextChanged(s);
            }
        });
        mSearchView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                onSearchFocusChanged(hasFocus);
            }
        });

        mSearchSuggestionsAdapter.setOnItemClickListener(this);

        Button speechButton;
        /*
        speechButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
            RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
            intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        //                intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,5);
            mSpeechRecognizer.startListening(intent);
            Log.i("111111", "11111111");
        }
        });
        */
    }

    void onSearchTextChanged(CharSequence charSequence) {
        mQuery = String.valueOf(charSequence);
        if (mQuery.length() > 0) {
            getLoaderManager().restartLoader(PEOPLE_SEARCH_LOADER, null, mPeopleCallbacks);
            getLoaderManager().restartLoader(APPS_SEARCH_LOADER, null, mAppsCallbacks);
            hideSearchSuggestions();
        } else {
            mQuery = null;
            mSearchAdapter.clear();
            showSearchSuggestions();
            getLoaderManager().destroyLoader(PEOPLE_SEARCH_LOADER);
            getLoaderManager().destroyLoader(APPS_SEARCH_LOADER);
        }
    }

    void onSearchFocusChanged(boolean hasFocus) {
        if (hasFocus) {
            showSearchSuggestions();
        }
    }

    private void hideSearchSuggestions() {
        mSearchRecyclerView.setVisibility(View.GONE);
        mSearchDivider.setVisibility(View.GONE);
    }

    private void showSearchSuggestions() {
        mSearchRecyclerView.setVisibility(View.VISIBLE);
        mSearchDivider.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onUserVisible() {
        super.onUserVisible();
        trackPageView(AnalyticsManager.CATEGORY_SEARCH);
        //        focusSearchView();
    }

    @Override
    protected void onUserInvisible() {
        super.onUserInvisible();
        InputMethodManager imm = mInputMethodManager;
        imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AppsiInjector.inject(this);
        mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getContext());
        mSpeechRecognizer.setRecognitionListener(new RecognitionListenerImpl());

        mSearchAdapter = new SearchAdapter(this, this, this);

        mPeopleCallbacks = new PeopleCallbacks();
        mAppsCallbacks = new AppsCallbacks();
        mRecentSearchedCallbacks = new RecentSearchedCallbacks();

        mSearchSuggestionsAdapter = new SearchSuggestionsAdapter();

        getLoaderManager().initLoader(RECENT_SEARCHES_LOADER, null, mRecentSearchedCallbacks);

    }

    @Override
    protected void applyToolbarColor(int color) {

    }

    public void setDeferLoads(boolean deferLoads) {
        super.setDeferLoads(deferLoads);
        if (!deferLoads) {
            //            focusSearchView();
        }
    }

    void focusSearchView() {
        if (mSearchView != null && isUserVisible()) {
            mSearchView.requestFocus();
            InputMethodManager imm = mInputMethodManager;
            imm.showSoftInput(mSearchView, InputMethodManager.SHOW_IMPLICIT);

        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        hideSearchSuggestions();
        SearchSuggestion searchSuggestion = mSearchSuggestionsAdapter.getItem(position);
        SearchSuggestionUtils.getInstance(getContext()).saveQuery(searchSuggestion.query);
        mSearchView.setText(searchSuggestion.query);
    }

    @Override
    public void onAppClicked(AppEntry app) {
        LauncherAppsCompat launcherApps = mLauncherAppsCompat;
        launcherApps.startActivityForProfile(app.getComponentName(), UserHandleCompat.myUserHandle(), null, null);
        // track a launch of the app in the app history
        ComponentName componentName = app.getComponentName();
        AppHistoryUtils.trackAppLaunch(getContext(), componentName);

        // track in google analytics
        track(AnalyticsManager.ACTION_OPEN_ITEM, AnalyticsManager.CATEGORY_APPS,
                app.getComponentName().flattenToShortString());

        SearchSuggestionUtils.getInstance(getContext()).saveQuery(mQuery);
    }

    @Override
    public void onPersonClicked(BaseContactInfo entry) {
        Uri uri = entry.mContactLookupUri;
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        track(AnalyticsManager.ACTION_OPEN_ITEM, AnalyticsManager.CATEGORY_SEARCH);
        getContext().startActivity(intent);
        SearchSuggestionUtils.getInstance(getContext()).saveQuery(mQuery);
    }

    @Override
    public void onEditSelected(BaseContactInfo entry) {
        Uri uri = entry.mContactLookupUri;
        Intent intent = new Intent(Intent.ACTION_EDIT, uri);
        track(AnalyticsManager.ACTION_EDIT_ITEM, AnalyticsManager.CATEGORY_SEARCH);
        getContext().startActivity(intent);
        SearchSuggestionUtils.getInstance(getContext()).saveQuery(mQuery);
    }

    private static class RecognitionListenerImpl implements RecognitionListener {

        RecognitionListenerImpl() {
        }

        @Override
        public void onReadyForSpeech(Bundle params) {

        }

        @Override
        public void onBeginningOfSpeech() {

        }

        @Override
        public void onRmsChanged(float rmsdB) {

        }

        @Override
        public void onBufferReceived(byte[] buffer) {

        }

        @Override
        public void onEndOfSpeech() {

        }

        @Override
        public void onError(int error) {

        }

        @Override
        public void onResults(Bundle results) {
            // implement result here
            List<String> voice = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
            // implement partial results here
        }

        @Override
        public void onEvent(int eventType, Bundle params) {

        }
    }

    static class SearchSuggestionViewHolder extends RecyclerView.ViewHolder {

        final TextView mTitleView;

        final AdapterView.OnItemClickListener mOnItemClickListener;

        public SearchSuggestionViewHolder(View itemView, final AdapterView.OnItemClickListener l) {
            super(itemView);
            mOnItemClickListener = l;
            mTitleView = (TextView) itemView.findViewById(R.id.recent_item_title);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.onItemClick(null, null, getPosition(), getItemId());
                }
            });
        }

        public void bind(SearchSuggestion item) {
            mTitleView.setText(item.query);
        }
    }

    static class SearchSuggestionsAdapter extends RecyclerView.Adapter<SearchSuggestionViewHolder> {

        final List<SearchSuggestion> mSearchSuggestions = new ArrayList<>();

        AdapterView.OnItemClickListener mOnItemClickListener;

        SearchSuggestionsAdapter() {
            setHasStableIds(true);
        }

        @Override
        public SearchSuggestionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(R.layout.list_item_recent_search, parent, false);
            return new SearchSuggestionViewHolder(view, mOnItemClickListener);
        }

        @Override
        public void onBindViewHolder(SearchSuggestionViewHolder holder, int position) {
            SearchSuggestion item = getItem(position);
            holder.bind(item);
        }

        public SearchSuggestion getItem(int position) {
            return mSearchSuggestions.get(position);
        }

        @Override
        public long getItemId(int position) {
            return mSearchSuggestions.get(position).id;
        }

        @Override
        public int getItemCount() {
            return mSearchSuggestions.size();
        }

        public void setItems(List<SearchSuggestion> suggestions) {
            mSearchSuggestions.clear();
            mSearchSuggestions.addAll(suggestions);
            notifyDataSetChanged();
        }

        public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
            mOnItemClickListener = listener;
        }
    }

    /**
     * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that
     * this class will always
     * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout
     * parameters.
     * <p/>
     * Now it's impossible to run add/remove animations with child views which have arbitrary
     * dimensions (height for
     * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
     * {@link #setChildSize(int)} method might be used to let the layout manager know how big
     * they are going to be.
     * If animations are not used at all then a normal measuring procedure will run and child
     * views will be measured during
     * the measure pass.
     */
    public static class WrapContentLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

        private static final int CHILD_WIDTH = 0;

        private static final int CHILD_HEIGHT = 1;

        private static final int DEFAULT_CHILD_SIZE = 100;

        private final int[] childDimensions = new int[2];

        private final RecyclerView view;

        private int childSize = DEFAULT_CHILD_SIZE;

        private boolean hasChildSize;

        private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;

        @SuppressWarnings("UnusedDeclaration")
        public WrapContentLinearLayoutManager(Context context) {
            super(context);
            this.view = null;
        }

        @SuppressWarnings("UnusedDeclaration")
        public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
            this.view = null;
        }

        @SuppressWarnings("UnusedDeclaration")
        public WrapContentLinearLayoutManager(RecyclerView view) {
            super(view.getContext());
            this.view = view;
            this.overScrollMode = ViewCompat.getOverScrollMode(view);
        }

        @SuppressWarnings("UnusedDeclaration")
        public WrapContentLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
            super(view.getContext(), orientation, reverseLayout);
            this.view = view;
            this.overScrollMode = ViewCompat.getOverScrollMode(view);
        }

        public static int makeUnspecifiedSpec() {
            return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        }

        public void setOverScrollMode(int overScrollMode) {
            if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER) {
                throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
            }
            if (this.view == null)
                throw new IllegalStateException("view == null");
            this.overScrollMode = overScrollMode;
            ViewCompat.setOverScrollMode(view, overScrollMode);
        }

        @Override
        public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
                int heightSpec) {
            final int widthMode = View.MeasureSpec.getMode(widthSpec);
            final int heightMode = View.MeasureSpec.getMode(heightSpec);

            final int widthSize = View.MeasureSpec.getSize(widthSpec);
            final int heightSize = View.MeasureSpec.getSize(heightSpec);

            final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
            final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

            final int unspecified = makeUnspecifiedSpec();

            if (exactWidth && exactHeight) {
                // in case of exact calculations for both dimensions let's use default
                // "onMeasure" implementation
                super.onMeasure(recycler, state, widthSpec, heightSpec);
                return;
            }

            final boolean vertical = getOrientation() == VERTICAL;

            initChildDimensions(widthSize, heightSize, vertical);

            int width = 0;
            int height = 0;

            // it's possible to get scrap views in recycler which are bound to old (invalid)
            // adapter entities. This
            // happens because their invalidation happens after "onMeasure" method. As a
            // workaround let's clear the
            // recycler now (it should not cause any performance issues while scrolling as
            // "onMeasure" is never
            // called whiles scrolling)
            recycler.clear();

            final int stateItemCount = state.getItemCount();
            final int adapterItemCount = getItemCount();
            // adapter always contains actual data while state might contain old data (f.e. data
            // before the animation is
            // done). As we want to measure the view with actual data we must use data from the
            // adapter and not from  the
            // state
            for (int i = 0; i < adapterItemCount; i++) {
                if (vertical) {
                    if (!hasChildSize) {
                        if (i < stateItemCount) {
                            // we should not exceed state count, otherwise we'll get
                            // IndexOutOfBoundsException. For such items
                            // we will use previously calculated dimensions
                            measureChild(recycler, i, widthSpec, unspecified, childDimensions);
                        } else {
                            logMeasureWarning(i);
                        }
                    }
                    height += childDimensions[CHILD_HEIGHT];
                    if (i == 0) {
                        width = childDimensions[CHILD_WIDTH];
                    }
                    if (height >= heightSize) {
                        break;
                    }
                } else {
                    if (!hasChildSize) {
                        if (i < stateItemCount) {
                            // we should not exceed state count, otherwise we'll get
                            // IndexOutOfBoundsException. For such items
                            // we will use previously calculated dimensions
                            measureChild(recycler, i, unspecified, heightSpec, childDimensions);
                        } else {
                            logMeasureWarning(i);
                        }
                    }
                    width += childDimensions[CHILD_WIDTH];
                    if (i == 0) {
                        height = childDimensions[CHILD_HEIGHT];
                    }
                    if (width >= widthSize) {
                        break;
                    }
                }
            }

            final boolean fit = (vertical && height < heightSize) || (!vertical && width < widthSize);
            if (fit) {
                // we really should wrap the contents of the view, let's do it

                if (exactWidth) {
                    width = widthSize;
                } else {
                    width += getPaddingLeft() + getPaddingRight();
                }

                if (exactHeight) {
                    height = heightSize;
                } else {
                    height += getPaddingTop() + getPaddingBottom();
                }

                setMeasuredDimension(width, height);
            } else {
                // if calculated height/width exceeds requested height/width let's use default
                // "onMeasure" implementation
                super.onMeasure(recycler, state, widthSpec, heightSpec);
            }

            if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
                ViewCompat.setOverScrollMode(view,
                        fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
            }
        }

        private void initChildDimensions(int width, int height, boolean vertical) {
            if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
                // already initialized, skipping
                return;
            }
            if (vertical) {
                childDimensions[CHILD_WIDTH] = width;
                childDimensions[CHILD_HEIGHT] = childSize;
            } else {
                childDimensions[CHILD_WIDTH] = childSize;
                childDimensions[CHILD_HEIGHT] = height;
            }
        }

        private void measureChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec,
                int[] dimensions) {
            final View child = recycler.getViewForPosition(position);

            final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

            final int hPadding = getPaddingLeft() + getPaddingRight();
            final int vPadding = getPaddingTop() + getPaddingBottom();

            final int hMargin = p.leftMargin + p.rightMargin;
            final int vMargin = p.topMargin + p.bottomMargin;

            final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
            final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

            final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width,
                    canScrollHorizontally());
            final int childHeightSpec = getChildMeasureSpec(heightSpec, vPadding + vMargin + vDecoration, p.height,
                    canScrollVertically());

            child.measure(childWidthSpec, childHeightSpec);

            dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
            dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

            recycler.recycleView(child);
        }

        private void logMeasureWarning(int child) {
            if (BuildConfig.DEBUG) {
                Log.w("LinearLayoutManager",
                        "Can't measure child #" + child + ", previously used dimensions will be reused."
                                + "To remove this message either use #setChildSize() method or don't run "
                                + "RecyclerView animations");
            }
        }

        @Override
        public void setOrientation(int orientation) {
            // might be called before the constructor of this class is called
            //noinspection ConstantConditions
            if (childDimensions != null) {
                if (getOrientation() != orientation) {
                    childDimensions[CHILD_WIDTH] = 0;
                    childDimensions[CHILD_HEIGHT] = 0;
                }
            }
            super.setOrientation(orientation);
        }

        public void clearChildSize() {
            hasChildSize = false;
            setChildSize(DEFAULT_CHILD_SIZE);
        }

        public void setChildSize(int childSize) {
            hasChildSize = true;
            if (this.childSize != childSize) {
                this.childSize = childSize;
                requestLayout();
            }
        }
    }

    class PeopleCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Uri baseUri;
            if (mQuery != null) {
                baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(mQuery));
            } else {
                baseUri = ContactsContract.Contacts.CONTENT_URI.buildUpon()
                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                                String.valueOf(ContactsContract.Directory.DEFAULT))
                        .build();
            }

            // Now create and return a CursorLoader that will take care of
            // creating a Cursor for the data being displayed.
            String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
            return new CursorLoader(getContext(), baseUri, PeopleQuery.CONTACTS_SUMMARY_PROJECTION, select, null,
                    ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }

        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            List<? extends BaseContactInfo> contactInfos = PeopleQuery.cursorToContactInfos(data);
            mSearchAdapter.setPeopleInfos(contactInfos);
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {

        }
    }

    class AppsCallbacks implements LoaderManager.LoaderCallbacks<List<AppEntry>> {

        @Override
        public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
            return new AppSearchLoader(getContext(), mLauncherAppsCompat, mQuery);

        }

        public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> apps) {
            mSearchAdapter.setApps(apps);
        }

        @Override
        public void onLoaderReset(Loader<List<AppEntry>> loader) {

        }
    }

    class RecentSearchedCallbacks implements LoaderManager.LoaderCallbacks<List<SearchSuggestion>> {

        @Override
        public Loader<List<SearchSuggestion>> onCreateLoader(int id, Bundle args) {
            return new RecentSearchLoader(getContext());

        }

        public void onLoadFinished(Loader<List<SearchSuggestion>> loader, List<SearchSuggestion> suggestions) {
            mSearchSuggestionsAdapter.setItems(suggestions);
        }

        @Override
        public void onLoaderReset(Loader<List<SearchSuggestion>> loader) {

        }
    }
}