FileDocCategorySizeDatePackage
SearchManagerTest.javaAPI DocAndroid 1.5 API21006Wed May 06 22:42:02 BST 2009com.android.unit_tests

SearchManagerTest.java

/*
 * Copyright (C) 2006 The Android Open Source 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 com.android.unit_tests;

import com.android.unit_tests.activity.LocalActivity;

import android.app.Activity;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
import android.server.search.SearchableInfo.ActionKeyInfo;
import android.test.ActivityInstrumentationTestCase;
import android.test.MoreAsserts;
import android.test.mock.MockContext;
import android.test.mock.MockPackageManager;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.AndroidRuntimeException;
import android.view.KeyEvent;

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

/**
 * To launch this test from the command line:
 * 
 * adb shell am instrument -w \
 *   -e class com.android.unit_tests.SearchManagerTest \
 *   com.android.unit_tests/android.test.InstrumentationTestRunner
 */
public class SearchManagerTest extends ActivityInstrumentationTestCase<LocalActivity> {
    
    // If non-zero, enable a set of tests that start and stop the search manager.
    // This is currently disabled because it's causing an unwanted jump from the unit test
    // activity into the contacts activity.  We'll put this back after we disable that jump.
    private static final int TEST_SEARCH_START = 0;
    
    /*
     * Bug list of test ideas.
     * 
     * testSearchManagerInterfaceAvailable()
     *  Exercise the interface obtained
     *  
     * testSearchManagerAvailable()
     *  Exercise the interface obtained
     *  
     * testSearchManagerInvocations()
     *  FIX - make it work again
     *  stress test with a very long string
     *  
     * SearchableInfo tests
     *  Mock the context so I can provide very specific input data
     *  Confirm OK with "zero" searchables
     *  Confirm "good" metadata read properly
     *  Confirm "bad" metadata skipped properly
     *  Confirm ordering of searchables
     *  Confirm "good" actionkeys
     *  confirm "bad" actionkeys are rejected
     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
     *  findActionKey works
     *  getIcon works
     * 
     * SearchManager tests
     *  confirm proper identification of "default" activity based on policy, not hardcoded contacts
     *  
     * SearchBar tests
     *  Maybe have to do with framework / unittest runner - need instrumented activity?
     *  How can we unit test the suggestions content providers?
     *  Should I write unit tests for any of them?
     *  Test scenarios:
     *    type-BACK (cancel)
     *    type-GO (send)
     *    type-navigate-click (suggestion)
     *    type-action
     *    type-navigate-action (suggestion)
     */
    
    /**
     * Local copy of activity context
     */
    Context mContext;

    public SearchManagerTest() {
        super("com.android.unit_tests", LocalActivity.class);
    }

    /**
     * Setup any common data for the upcoming tests.
     */
    @Override
    public void setUp() throws Exception {
        super.setUp();
        
        Activity testActivity = getActivity();
        mContext = (Context)testActivity;
    }

    /**
     * The goal of this test is to confirm that we can obtain
     * a search manager interface.
     */
    @MediumTest
    public void testSearchManagerInterfaceAvailable() {
        ISearchManager searchManager1 = ISearchManager.Stub.asInterface(
                ServiceManager.getService(Context.SEARCH_SERVICE));
        assertNotNull(searchManager1);
    }
    
    /**
     * The goal of this test is to confirm that we can *only* obtain a search manager
     * interface from an Activity context.
     */
    @MediumTest
    public void testSearchManagerContextRestrictions() {
        SearchManager searchManager1 = (SearchManager)
                mContext.getSystemService(Context.SEARCH_SERVICE);
        assertNotNull(searchManager1);
        
        Context applicationContext = mContext.getApplicationContext();
        // this should fail, because you can't get a SearchManager from a non-Activity context
        try {
            applicationContext.getSystemService(Context.SEARCH_SERVICE);
            assertFalse("Shouldn't retrieve SearchManager from a non-Activity context", true);
        } catch (AndroidRuntimeException e) {
            // happy here - we should catch this.
        }
    }
    
    /**
     * The goal of this test is to confirm that we can obtain
     * a search manager at any time, and that for any given context,
     * it is a singleton.
     */
    @LargeTest
    public void testSearchManagerAvailable() {
        SearchManager searchManager1 = (SearchManager)
                mContext.getSystemService(Context.SEARCH_SERVICE);
        assertNotNull(searchManager1);
        SearchManager searchManager2 = (SearchManager)
                mContext.getSystemService(Context.SEARCH_SERVICE);
        assertNotNull(searchManager2);
        assertSame( searchManager1, searchManager2 );
    }
    
    /**
     * The goal of this test is to confirm that we can start and then
     * stop a simple search.
     */
    
   @MediumTest
   public void testSearchManagerInvocations() {
        SearchManager searchManager = (SearchManager)
                mContext.getSystemService(Context.SEARCH_SERVICE);
        assertNotNull(searchManager);
        
            // TODO: make a real component name, or remove this need
        final ComponentName cn = new ComponentName("", "");

        if (TEST_SEARCH_START != 0) {
            // These tests should simply run to completion w/o exceptions
            searchManager.startSearch(null, false, cn, null, false);
            searchManager.stopSearch();
            
            searchManager.startSearch("", false, cn, null, false);
            searchManager.stopSearch();
            
            searchManager.startSearch("test search string", false, cn, null, false);
            searchManager.stopSearch();
            
            searchManager.startSearch("test search string", true, cn, null, false);
            searchManager.stopSearch();
        }
     }
    
    /**
     * The goal of this test is to confirm proper operation of the 
     * SearchableInfo helper class.
     * 
     * TODO:  The metadata source needs to be mocked out because adding
     * searchability metadata via this test is causing it to leak into the
     * real system.  So for now I'm just going to test for existence of the
     * GoogleSearch app (which is searchable).
     */
    @LargeTest
    public void testSearchableGoogleSearch() {
        // test basic array & hashmap
        SearchableInfo.buildSearchableList(mContext);

        // test linkage from another activity
        // TODO inject this via mocking into the package manager.
        // TODO for now, just check for searchable GoogleSearch app (this isn't really a unit test)
        ComponentName thisActivity = new ComponentName(
                "com.android.googlesearch", 
                "com.android.googlesearch.GoogleSearch");

        SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, thisActivity);
        assertNotNull(si);
        assertTrue(si.mSearchable);
        assertEquals(thisActivity, si.mSearchActivity);
        
        Context appContext = si.getActivityContext(mContext);
        assertNotNull(appContext);
        MoreAsserts.assertNotEqual(appContext, mContext);
        assertEquals("Google Search", appContext.getString(si.getHintId()));
        assertEquals("Google", appContext.getString(si.getLabelId()));
    }
    
    /**
     * Test that non-searchable activities return no searchable info (this would typically
     * trigger the use of the default searchable e.g. contacts)
     */
    @LargeTest
    public void testNonSearchable() {
        // test basic array & hashmap
        SearchableInfo.buildSearchableList(mContext);

        // confirm that we return null for non-searchy activities
        ComponentName nonActivity = new ComponentName(
                            "com.android.unit_tests",
                            "com.android.unit_tests.NO_SEARCH_ACTIVITY");
        SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, nonActivity);
        assertNull(si);
    }
    
    /**
     * This is an attempt to run the searchable info list with a mocked context.  Here are some
     * things I'd like to test.
     *
     *  Confirm OK with "zero" searchables
     *  Confirm "good" metadata read properly
     *  Confirm "bad" metadata skipped properly
     *  Confirm ordering of searchables
     *  Confirm "good" actionkeys
     *  confirm "bad" actionkeys are rejected
     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
     *  findActionKey works
     *  getIcon works

     */
    @LargeTest
    public void testSearchableMocked() {
        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
        ArrayList<SearchableInfo> searchables;
        int count;

        // build item list with real-world source data
        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
        SearchableInfo.buildSearchableList(mockContext);
        // tests with "real" searchables (deprecate, this should be a unit test)
        searchables = SearchableInfo.getSearchablesList();
        count = searchables.size();
        assertTrue(count >= 1);         // this isn't really a unit test
        checkSearchables(searchables);

        // build item list with mocked search data
        // this round of tests confirms good operations with "zero" searchables found
        // This should return either a null pointer or an empty list
        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
        SearchableInfo.buildSearchableList(mockContext);
        searchables = SearchableInfo.getSearchablesList();
        if (searchables != null) {
            count = searchables.size();
            assertTrue(count == 0);
        }
    }
    
    /**
     * Generic health checker for an array of searchables.
     * 
     * This is designed to pass for any semi-legal searchable, without knowing much about
     * the format of the underlying data.  It's fairly easy for a non-compliant application
     * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
     * 
     * @param searchables The list of searchables to examine.
     */
    private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
        assertNotNull(searchablesList);
        int count = searchablesList.size();
        for (int ii = 0; ii < count; ii++) {
            SearchableInfo si = searchablesList.get(ii);
            assertNotNull(si);
            assertTrue(si.mSearchable);
            assertTrue(si.getLabelId() != 0);        // This must be a useable string
            assertNotEmpty(si.mSearchActivity.getClassName());
            assertNotEmpty(si.mSearchActivity.getPackageName());
            if (si.getSuggestAuthority() != null) {
                // The suggestion fields are largely optional, so we'll just confirm basic health
                assertNotEmpty(si.getSuggestAuthority());
                assertNullOrNotEmpty(si.getSuggestPath());
                assertNullOrNotEmpty(si.getSuggestSelection());
                assertNullOrNotEmpty(si.getSuggestIntentAction());
                assertNullOrNotEmpty(si.getSuggestIntentData());
            }
            /* Add a way to get the entire action key list, then explicitly test its elements */
            /* For now, test the most common action key (CALL) */
            ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
            if (ai != null) {
                assertEquals(ai.mKeyCode, KeyEvent.KEYCODE_CALL);
                // one of these three fields must be non-null & non-empty
                boolean m1 = (ai.mQueryActionMsg != null) && (ai.mQueryActionMsg.length() > 0);
                boolean m2 = (ai.mSuggestActionMsg != null) && (ai.mSuggestActionMsg.length() > 0);
                boolean m3 = (ai.mSuggestActionMsgColumn != null) && 
                                (ai.mSuggestActionMsgColumn.length() > 0);
                assertTrue(m1 || m2 || m3);
            }
            
            /* 
             * Find ways to test these:
             * 
             * private int mSearchMode
             * private Drawable mIcon
             */
            
            /*
             * Explicitly not tested here:
             * 
             * Can be null, so not much to see:
             * public String mSearchHint
             * private String mZeroQueryBanner
             * 
             * To be deprecated/removed, so don't bother:
             * public boolean mFilterMode
             * public boolean mQuickStart
             * private boolean mIconResized
             * private int mIconResizeWidth
             * private int mIconResizeHeight
             * 
             * All of these are "internal" working variables, not part of any contract
             * private ActivityInfo mActivityInfo
             * private Rect mTempRect
             * private String mSuggestProviderPackage
             * private String mCacheActivityContext
             */
        }
    }
    
    /**
     * Combo assert for "string not null and not empty"
     */
    private void assertNotEmpty(final String s) {
        assertNotNull(s);
        MoreAsserts.assertNotEqual(s, "");
    }
    
    /**
     * Combo assert for "string null or (not null and not empty)"
     */
    private void assertNullOrNotEmpty(final String s) {
        if (s != null) {
            MoreAsserts.assertNotEqual(s, "");
        }
    }    
    
    /**
     * This is a mock for context.  Used to perform a true unit test on SearchableInfo.
     * 
     */
    private class MyMockContext extends MockContext {
        
        protected Context mRealContext;
        protected PackageManager mPackageManager;
        
        /**
         * Constructor.
         * 
         * @param realContext Please pass in a real context for some pass-throughs to function.
         */
        MyMockContext(Context realContext, PackageManager packageManager) {
            mRealContext = realContext;
            mPackageManager = packageManager;
        }
        
        /**
         * Resources.  Pass through for now.
         */
        @Override
        public Resources getResources() {
            return mRealContext.getResources();
        }

        /**
         * Package manager.  Pass through for now.
         */
        @Override
        public PackageManager getPackageManager() {
            return mPackageManager;
        }

        /**
         * Package manager.  Pass through for now.
         */
        @Override
        public Context createPackageContext(String packageName, int flags)
                throws PackageManager.NameNotFoundException {
            return mRealContext.createPackageContext(packageName, flags);
        }
    }

/**
 * This is a mock for package manager.  Used to perform a true unit test on SearchableInfo.
 * 
 */
    private class MyMockPackageManager extends MockPackageManager {
        
        public final static int SEARCHABLES_PASSTHROUGH = 0;
        public final static int SEARCHABLES_MOCK_ZERO = 1;
        public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
        public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
        
        protected PackageManager mRealPackageManager;
        protected int mSearchablesMode;

        public MyMockPackageManager(PackageManager realPM) {
            mRealPackageManager = realPM;
            mSearchablesMode = SEARCHABLES_PASSTHROUGH;
        }

        /**
         * Set the mode for various tests.
         */
        public void setSearchablesMode(int newMode) {
            switch (newMode) {
            case SEARCHABLES_PASSTHROUGH:
            case SEARCHABLES_MOCK_ZERO:
                mSearchablesMode = newMode;
                break;
                
            default:
                throw new UnsupportedOperationException();       
            }
        }
        
        /**
         * Find activities that support a given intent.
         * 
         * Retrieve all activities that can be performed for the given intent.
         * 
         * @param intent The desired intent as per resolveActivity().
         * @param flags Additional option flags.  The most important is
         *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
         *                    those activities that support the CATEGORY_DEFAULT.
         * 
         * @return A List<ResolveInfo> containing one entry for each matching
         *         Activity. These are ordered from best to worst match -- that
         *         is, the first item in the list is what is returned by
         *         resolveActivity().  If there are no matching activities, an empty
         *         list is returned.
         */
        @Override 
        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
            assertNotNull(intent);
            assertEquals(intent.getAction(), Intent.ACTION_SEARCH);
            switch (mSearchablesMode) {
            case SEARCHABLES_PASSTHROUGH:
                return mRealPackageManager.queryIntentActivities(intent, flags);
            case SEARCHABLES_MOCK_ZERO:
                return null;
            default:
                throw new UnsupportedOperationException();
            }
        }
        
        /**
         * Retrieve an XML file from a package.  This is a low-level API used to
         * retrieve XML meta data.
         * 
         * @param packageName The name of the package that this xml is coming from.
         * Can not be null.
         * @param resid The resource identifier of the desired xml.  Can not be 0.
         * @param appInfo Overall information about <var>packageName</var>.  This
         * may be null, in which case the application information will be retrieved
         * for you if needed; if you already have this information around, it can
         * be much more efficient to supply it here.
         * 
         * @return Returns an XmlPullParser allowing you to parse out the XML
         * data.  Returns null if the xml resource could not be found for any
         * reason.
         */
        @Override 
        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
            assertNotNull(packageName);
            MoreAsserts.assertNotEqual(packageName, "");
            MoreAsserts.assertNotEqual(resid, 0);
            switch (mSearchablesMode) {
            case SEARCHABLES_PASSTHROUGH:
                return mRealPackageManager.getXml(packageName, resid, appInfo);
            case SEARCHABLES_MOCK_ZERO:
            default:
                throw new UnsupportedOperationException();
            }
        }
        
        /**
         * Find a single content provider by its base path name.
         * 
         * @param name The name of the provider to find.
         * @param flags Additional option flags.  Currently should always be 0.
         * 
         * @return ContentProviderInfo Information about the provider, if found,
         *         else null.
         */
        @Override 
        public ProviderInfo resolveContentProvider(String name, int flags) {
            assertNotNull(name);
            MoreAsserts.assertNotEqual(name, "");
            assertEquals(flags, 0);
            switch (mSearchablesMode) {
            case SEARCHABLES_PASSTHROUGH:
                return mRealPackageManager.resolveContentProvider(name, flags);
            case SEARCHABLES_MOCK_ZERO:
            default:
                throw new UnsupportedOperationException();
            }
        }
    }
}