pro android 5 - lagout android 5 [maclean... · pro android 5 dave maclean satya komatineni ... and...

Post on 19-Apr-2018

238 Views

Category:

Documents

14 Downloads

Preview:

Click to see full reader

TRANSCRIPT

ProAndroid5

DaveMacLean

SatyaKomatineni

GrantAllen

ProAndroid5

Copyright©2015byDaveMacLean,SatyaKomatineni,andGrantAllen

Thisworkissubjecttocopyright.AllrightsarereservedbythePublisher,whetherthewholeorpartofthematerialisconcerned,specificallytherightsoftranslation,reprinting,reuseofillustrations,recitation,broadcasting,reproductiononmicrofilmsorinanyotherphysicalway,andtransmissionorinformationstorageandretrieval,electronicadaptation,computersoftware,orbysimilarordissimilarmethodologynowknownorhereafterdeveloped.Exemptedfromthislegalreservationarebriefexcerptsinconnectionwithreviewsorscholarlyanalysisormaterialsuppliedspecificallyforthepurposeofbeingenteredandexecutedonacomputersystem,forexclusiveusebythepurchaserofthework.DuplicationofthispublicationorpartsthereofispermittedonlyundertheprovisionsoftheCopyrightLawofthePublisher’slocation,initscurrentversion,andpermissionforusemustalwaysbeobtainedfromSpringer.PermissionsforusemaybeobtainedthroughRightsLinkattheCopyrightClearanceCenter.ViolationsareliabletoprosecutionundertherespectiveCopyrightLaw.

ISBN-13(pbk):978-1-4302-4680-0

ISBN-13(electronic):978-1-4302-4681-7

Trademarkednames,logos,andimagesmayappearinthisbook.Ratherthanuseatrademarksymbolwitheveryoccurrenceofatrademarkedname,logo,orimageweusethenames,logos,andimagesonlyinaneditorialfashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.

TheimagesoftheAndroidRobot(01/AndroidRobot)arereproducedfromworkcreatedandsharedbyGoogleandusedaccordingtotermsdescribedintheCreativeCommons3.0AttributionLicense.AndroidandallAndroidandGoogle-basedmarksaretrademarksorregisteredtrademarksofGoogleInc.intheUnitedStatesandothercountries.ApressMediaLLCisnotaffiliatedwithGoogleInc.,andthisbookwaswrittenwithoutendorsementfromGoogleInc.

Theuseinthispublicationoftradenames,trademarks,servicemarks,andsimilarterms,eveniftheyarenotidentifiedassuch,isnottobetakenasanexpressionofopinionastowhetherornottheyaresubjecttoproprietaryrights.

Whiletheadviceandinformationinthisbookarebelievedtobetrueandaccurateatthedateofpublication,neithertheauthorsnortheeditorsnorthepublishercanacceptanylegalresponsibilityforanyerrorsoromissionsthatmaybemade.Thepublishermakesnowarranty,expressorimplied,withrespecttothematerialcontainedherein.

ManagingDirector:WelmoedSpahr

LeadEditor:SteveAnglin

TechnicalReviewer:ShaneKirk

EditorialBoard:SteveAnglin,LouiseCorrigan,JonathanGennick,RobertHutchinson,MichelleLowman,JamesMarkham,Susan

McDermott,MatthewMoodie,JeffreyPepper,DouglasPundick,BenRenow-Clarke,GwenanSpearing,SteveWeiss

CoordinatingEditor:MarkPowers

CopyEditor:BrendanFrost

Compositor:SPiGlobal

Indexer:SPiGlobal

Artist:SPiGlobal

CoverDesigner:AnnaIshchenko

DistributedtothebooktradeworldwidebySpringerScience+BusinessMediaNewYork,233SpringStreet,6thFloor,NewYork,NY10013.Phone1-800-SPRINGER,fax(201)348-4505,e-mailorders-ny@springer-sbm.com,orvisitwww.springeronline.com.ApressMedia,LLCisaCaliforniaLLCandthesolemember(owner)isSpringerScience+BusinessMediaFinanceInc(SSBMFinanceInc).SSBMFinanceIncisaDelawarecorporation.

Forinformationontranslations,pleasee-mailrights@apress.com,orvisitwww.apress.com.

ApressandfriendsofEDbooksmaybepurchasedinbulkforacademic,corporate,orpromotionaluse.eBookversionsandlicensesarealsoavailableformosttitles.Formoreinformation,referenceourSpecialBulkSales–eBookLicensingwebpageatwww.apress.com/bulk-sales.

Anysourcecodeorothersupplementarymaterialreferencedbytheauthorinthistextisavailabletoreadersatwww.apress.com/9781430246800.Fordetailedinformationabouthowtolocateyourbook’ssourcecode,gotowww.apress.com/source-code/.

TomywifeRosie,Ineverwouldhavemadeitthisfarwithoutyourloveandsupport.Thankyou,youarewonderful,andIloveyou.

—DaveMacLean

TomylateyoungerbrotherSankarKomatineniwhoseindustry,hardship,andzestforlifefillsmewithsadnessandjoy.

—SatyaKomatineni

ToalltheAndroiddevelopersouttheredreamingofthenextgreatAndroidapp!Youareanamazingcommunity,andIcan’twaittoseewhatyou

developnext.

—GrantAllen

ContentsataGlanceAbouttheAuthors

AbouttheTechnicalReviewer

Acknowledgments

Foreword

Introduction

Chapter1:HelloAndroid

Chapter2:IntroductiontoAndroidApplicationArchitecture

Chapter3:BuildingBasicUserInterfacesandUsingControls

Chapter4:AdaptersandListControls

Chapter5:BuildingMoreAdvancedUILayouts

Chapter6:WorkingwithMenusandActionBars

Chapter7:StylesandThemes

Chapter8:Fragments

Chapter9:RespondingtoConfigurationChanges

Chapter10:WorkingwithDialogs

Chapter11:WorkingwithPreferencesandSavingState

Chapter12:UsingtheCompatibilityLibraryforOlderDevices

Chapter13:ExploringPackages,Processes,Threads,andHandlers

Chapter14:BuildingandConsumingServices

Chapter15:AdvancedAsyncTaskandProgressDialogs

Chapter16:BroadcastReceiversandLong-RunningServices

Chapter17:ExploringtheAlarmManager

Chapter18:Exploring2DAnimation

Chapter19:ExploringMapsandLocation-BasedServices

Chapter20:UnderstandingtheMediaFrameworks

Chapter21:HomeScreenWidgets

Chapter22:TouchScreens

Chapter23:ImplementingDragandDrop

Chapter24:UsingSensors

Chapter25:ExploringAndroidPersistenceandContentProviders

Chapter26:UnderstandingLoaders

Chapter27:ExploringtheContactsAPI

Chapter28:ExploringSecurityandPermissions

Chapter29:UsingGoogleCloudMessagingwithAndroid

Chapter30:DeployingYourApplication:GooglePlayStoreandBeyond

Index

ContentsAbouttheAuthors

AbouttheTechnicalReviewer

Acknowledgments

Foreword

Introduction

Chapter1:HelloAndroid

PrerequisitesforAndroidDevelopment

SettingUpYourEclipseEnvironmentDownloadingJDK6

DownloadingEclipse4.2

DownloadingtheAndroidSDK

TheToolsWindow

InstallingADT

SettingUpYourAndroidStudioEnvironmentJavarequirementsforAndroidStudio

DownloadingandInstallingAndroidStudio

LearningAndroid’sFundamentalComponentsView

Activity

Fragment

Intent

ContentProvider

Service

AndroidManifest.xml

AVDs

HelloWorld!

AVDs

RunningonaRealDevice

ExploringtheStructureofanAndroidApplication

ExaminingtheApplicationLifeCycle

SimpleDebugging

LaunchingtheEmulator

References

Summary

Chapter2:IntroductiontoAndroidApplicationArchitecture

ExploringaSimpleAndroidApplication

DefiningUIthroughLayoutFilesSpecifyingCommentsinLayoutFiles

AddingViewsandViewGroupsinLayoutFiles

SpecifyingControlPropertiesinLayoutFiles

IndicatingViewGroupProperties

ControllingWidthandHeightofaControl

IntroducingResourcesandBackgrounds

WorkingwithTextControlsintheLayoutFile

WorkingwithAutogeneratedIDsforControls

ImplementingProgrammingLogicLoadingtheLayoutFileintoanActivity

GatheringControls

SettingUpButtons

RespondingtoButtonClicks:TyingItAllTogether

UpdatingtheAndroidManifest.XML

PlacingtheFilesintheAndroidProject

TestingtheCalculatorApponaRealDevice

AndroidActivityLifeCyclevoidonCreate(BundlesavedInstanceState)

voidonStart()

voidonRestoreInstanceState(BundlesavedInstanceState)

voidonResume()

voidonPause()

voidonStop()

voidonSaveInstanceState(BundlesaveStateBundle)

voidonRestart()

ObjectonRetainNonConfigurationInstance()

voidonDestroy()

GeneralNotesonActivityCallbacks

MoreonResourcesDirectoryStructureofResources

ReadingResourcesfromJavaCode

RuntimeBehaviorofDrawableResources

UsingArbitraryXMLFilesasResources

WorkingwithRawResourceFiles

ReadingFilesfromtheAssetsDirectory

ReadingResourcesandAssetsWithoutanActivityReference

UnderstandingResourceDirectories,Language,andLocale

MoreonIntentsStartingActivitiesforResults

ExercisingtheGET_CONTENTAction

RelatingIntentsandActivities

UnderstandingExplicitandImplicitIntents

SavingStateinAndroid

RoadmapforLearningAndroidandtheRestoftheBookTrack1:UIEssentialsforYourAndroidApplications

Track2:SavingState

Track3:Preparing/TakingYourApplicationtotheMarket

Track4:MakingYourApplicationRobust

Track5:BringingFinessetoYourApps

Track6:IntegratingwithOtherDevicesandtheCloud

FinalTrack:GettingaHelpingHandfromExpertAndroid

AsWeLeaveYouNowwiththeRestoftheBook

References

Summary

Chapter3:BuildingBasicUserInterfacesandUsingControls

UIDevelopmentinAndroidBuildingaUICompletelyinCode

BuildingaUICompletelyinXML

BuildingaUIinXMLwithCode

UnderstandingAndroid’sCommonControlsTextControls

ButtonControls

TheImageViewControl

DateandTimeControls

TheMapViewControl

References

Summary

Chapter4:AdaptersandListControls

UnderstandingAdaptersGettingtoKnowSimpleCursorAdapter

GettingtoKnowArrayAdapter

UsingAdapterswithAdapterViewsTheBasicListControl:ListView

TheGridViewControl

TheSpinnerControl

TheGalleryControl

Summary

Chapter5:BuildingMoreAdvancedUILayouts

CreatingCustomAdaptersOtherControlsinAndroid

StylesandThemesUsingStyles

UsingThemes

UnderstandingLayoutManagersTheLinearLayoutLayoutManager

TheTableLayoutLayoutManager

TheRelativeLayoutLayoutManager

TheFrameLayoutLayoutManager

TheGridLayoutLayoutManager

CustomizingtheLayoutforVariousDeviceConfigurations

Summary

Chapter6:WorkingwithMenusandActionBars

WorkingwithMenusThroughXMLFilesCreatingXMLMenuResourceFiles

PopulatingActivityMenufromMenuXMLFiles

RespondingtoXML-BasedMenuItems

WorkingwithMenusinJavaCodeWorkingwithMenuGroups

UnderstandingExpandedMenus

WorkingwithIconMenus

WorkingwithSubmenus

WorkingwithContextMenusRegisteringaViewforaContextMenu

PopulatingaContextMenu

RespondingtoContextMenuItems

IncorporatingDynamicMenusWorkingwithPop-upMenus

ExploringActionBars

ImplementingaStandardActionBar

ImplementingaTabbedActionBar

ImplementingaList-BasedActionBar

ExploringActionBarandSearchViewDefiningaSearchViewWidgetasaMenuItem

CreatingaSearchResultsActivity

SpecifyingaSearchableXMLFile

DefiningtheSearchResultsActivityintheManifestFile

IdentifyingtheSearchTargetfortheSearchViewWidget

Resources

Summary

Chapter7:StylesandThemes

UsingStyles

UsingThemes

References

Summary

Chapter8:Fragments

WhatIsaFragment?WhentoUseFragments

TheStructureofaFragment

AFragment’sLifeCycle

SampleFragmentAppShowingtheLifeCycle

FragmentTransactionsandtheFragmentBackStack

FragmentTransactionTransitionsandAnimations

TheFragmentManagerCautionWhenReferencingFragments

SavingFragmentState

ListFragmentsand<fragment>

InvokingaSeparateActivityWhenNeeded

PersistenceofFragments

CommunicationswithFragmentsUsingstartActivity()andsetTargetFragment()

References

Summary

Chapter9:RespondingtoConfigurationChanges

TheDefaultConfigurationChangeProcessTheDestroy/CreateCycleofActivities

TheDestroy/CreateCycleofFragments

UsingFragmentManagertoSaveFragmentState

UsingsetRetainInstanceonaFragment

DeprecatedConfigurationChangeMethods

HandlingConfigurationChangesYourself

References

Summary

Chapter10:WorkingwithDialogs

UsingDialogsinAndroid

UnderstandingDialogFragmentsDialogFragmentBasics

DialogFragmentSampleApplication

WorkingwithToast

References

Summary

Chapter11:WorkingwithPreferencesandSavingState

ExploringthePreferencesFrameworkUnderstandingCheckBoxPreferenceandSwitchPreference

AccessingaPreferenceValueinCode

UnderstandingListPreference

UnderstandingEditTextPreference

UnderstandingMultiSelectListPreference

UpdatingAndroidManifest.xml

UsingPreferenceCategory

CreatingChildPreferenceswithDependency

PreferenceswithHeaders

PreferenceScreens

DynamicPreferenceSummaryTextSavingStatewithPreferences

UsingDialogPreference

Reference

Summary

Chapter12:UsingtheCompatibilityLibraryforOlderDevices

ItAllStartedwithTablets

AddingtheLibrarytoYourProjectIncludingthev7SupportLibrary

Includingthev8SupportLibrary

Includingthev13SupportLibrary

Includingthev17SupportLibrary

IncludingJustthev4SupportLibrary

RetrofittinganAppwiththeAndroidSupportLibrary

References

Summary

Chapter13:ExploringPackages,Processes,Threads,andHandlers

UnderstandingPackagesandProcesses

ACodePatternforSharingData

UnderstandingLibraryProjects

UnderstandingComponentsandThreads

UnderstandingHandlers

UsingWorkerThreads

References

Summary

Chapter14:BuildingandConsumingServices

ConsumingHTTPServicesUsingtheHttpClientforHTTPGETRequests

UsingtheHttpClientforHTTPPOSTRequests(aMultipartExample)

SOAP,JSON,andXMLParsers

DealingwithExceptions

AddressingMultithreadingIssues

FunwithTimeouts

UsingtheHttpURLConnection

UsingtheAndroidHttpClient

UsingAndroidServicesUnderstandingServicesinAndroid

UnderstandingLocalServices

UnderstandingAIDLServices

DefiningaServiceInterfaceinAIDL

ImplementinganAIDLInterface

CallingtheServicefromaClientApplication

PassingComplexTypestoServices

MessengersandHandlers

References

Summary

Chapter15:AdvancedAsyncTaskandProgressDialogs

EssentialsofaSimpleAsyncTaskImplementingYourFirstAsyncTask

CallinganAsyncTask

UnderstandingtheonPreExecute()CallbackandProgressDialog

UnderstandingthedoInBackground()Method

TriggeringonProgressUpdate()throughpublishProgress()

UnderstandingtheonPostExecute()Method

UpgradingtoaDeterministicProgressDialog

AsyncTaskandThreadPools

IssuesandSolutionsforCorrectlyShowingtheProgressofanAsyncTaskDealingwithActivityPointersandDeviceRotation

DealingwithManagedDialogs

UsingRetainedObjectsandFragmentDialogs

UsingRetainedFragmentsandFragmentDialogs

UsingRetainedFragmentsandProgressBars

References

Summary

Chapter16:BroadcastReceiversandLong-RunningServices

SendingaBroadcastCodingaSimpleReceiver

RegisteringaReceiverintheManifestFile

AccommodatingMultipleReceivers

WorkingwithOut-of-ProcessReceivers

UsingNotificationsfromaReceiverMonitoringNotificationsThroughtheNotificationManager

SendingaNotification

StartinganActivityinaBroadcastReceiver

ExploringLong-RunningReceiversandServicesUnderstandingLong-RunningBroadcastReceiverProtocol

UnderstandingIntentService

ExtendingIntentServiceforaBroadcastReceiverExploringLong-RunningBroadcastServiceAbstraction

DesigningALong-RunningReceiver

AbstractingaWakeLockwithLightedGreenRoom

ImplementingaLong-RunningServiceUnderstandingaNonstickyService

UnderstandingaStickyService

UnderstandingRedeliverIntentsOption

CodingaLong-RunningService

AdditionalTopicsinBroadcastReceivers

References

Summary

Chapter17:ExploringtheAlarmManager

SettingUpaSimpleAlarmSettingOffanAlarmRepeatedly

CancellinganAlarm

UnderstandingExactnessofAlarms

UnderstandingPersistenceofAlarms

References

Summary

Chapter18:Exploring2DAnimation

ExploringFrame-by-FrameAnimation

ExploringLayoutAnimationUnderstandingInterpolators

ExploringViewAnimationUsingCameratoProvideDepthPerceptionin2D

ExploringtheAnimationListenerClass

NotesonTransformationMatrices

ExploringPropertyAnimations:TheNewAnimationAPIUnderstandingPropertyAnimation

PlanningaTestBedforPropertyAnimation

AnimatingViewswithObjectAnimators

AchievingSequentialAnimationwithAnimatorSet

SettingAnimationRelationshipswithAnimatorSet.Builder

UsingXMLtoLoadAnimators

UsingPropertyValuesHolder

UnderstandingViewPropertiesAnimation

UnderstandingTypeEvaluators

UnderstandingKeyFrames

UnderstandingLayoutTransitions

Resources

Summary

Chapter19:ExploringMapsandLocation-BasedServices

UnderstandingtheMappingPackageObtainingaMapsAPIKeyfromGoogle

AddingtheMapsAPIKeytoYourApplication

UnderstandingMapFragment

AddingMarkerstoMaps

UnderstandingtheLocationPackageGeocodingwithAndroid

UnderstandingLocationServices

UsingProximityAlertsandGeofencing

References

Summary

Chapter20:UnderstandingtheMediaFrameworks

UsingtheMediaAPIsWhitherSDCards?

PlayingMediaPlayingAudioContent

PlayingVideoContent

BonusOnlineChapteronRecordingandAdvancedMediaReferences

Summary

Chapter21:HomeScreenWidgets

UserExperiencewithHomeScreenWidgets

UnderstandingWidgetConfigurationActivity

UnderstandingtheLifeCycleofaWidgetUnderstandingWidgetDefinitionPhase

ImplementingASampleWidgetApplicationDefiningtheWidgetProvider

ImplementingWidgetConfigurationActivity

ImplementingaWidgetProvider

Collection-BasedWidgets

Resources

Summary

Chapter22:TouchScreens

UnderstandingMotionEventsTheMotionEventObject

RecyclingMotionEvents

UsingVelocityTracker

MultitouchTheBasicsofMultitouch

Gestures

ThePinchGesture

GestureDetectorandOnGestureListeners

References

Summary

Chapter23:ImplementingDragandDrop

ExploringDragandDrop

BasicsofDragandDropin3.0+

Drag-and-DropExampleApplicationListofFiles

LayingOuttheExampleDrag-and-DropApplication

RespondingtoonDragintheDropzone

SettingUptheDragSourceViews

TestingtheExampleDrag-and-DropApplication

References

Summary

Chapter24:UsingSensors

WhatIsaSensor?DetectingSensors

WhatCanWeKnowAboutaSensor?

GettingSensorEventsIssueswithGettingSensorData

InterpretingSensorDataLightSensors

ProximitySensors

TemperatureSensors

PressureSensors

GyroscopeSensors

Accelerometers

MagneticFieldSensors

UsingAccelerometersandMagneticFieldSensorsTogether

MagneticDeclinationandGeomagneticField

GravitySensors

LinearAccelerationSensors

RotationVectorSensors

References

Summary

Chapter25:ExploringAndroidPersistenceandContentProvidersSavingStateUsingSharedPreferences

SavingStateUsingInternalFiles

SavingStateUsingExternalFiles

SavingStateUsingSQLite

SavingStateUsingO/RMappingLibraries

SavingStateUsingContentProviders

SavingStateUsingNetworkStorage

StoringDataDirectlyUsingSQLiteSummarizingKeySQLitePackagesandClasses

CreatinganSQLiteDatabase

DefiningaDatabaseThroughDDLs

MigratingaDatabase

InsertingRows

UpdatingRows

DeletingRows

ReadingRows

ApplyingTransactions

SummarizingSQLite

DoingTransactionsThroughDynamicProxies

ExploringDatabasesontheEmulatorandAvailableDevices

ExploringContentProvidersExploringAndroid’sBuilt-inProviders

UnderstandingtheStructureofContentProviderURIs

ImplementingContentProvidersPlanningaDatabase

ExtendingContentProvider

UsingUriMatchertoFigureOuttheURIs

UsingProjectionMaps

FulfillingMIME-TypeContracts

ImplementingtheQueryMethod

ImplementingtheInsertMethod

ImplementingtheUpdateMethod

ImplementingtheDeleteMethod

RegisteringtheProvider

ExercisingtheBookProvider

AddingaBook

RemovingaBook

DisplayingtheListofBooks

Resources

Summary

Chapter26:UnderstandingLoaders

UnderstandingtheArchitectureofLoaders

ListingBasicLoaderAPIClasses

DemonstratingtheLoaders

Step1:PreparingtheActivitytoLoadData

Step2:InitializingtheLoaderDelvingintotheStructureofListActivity

WorkingwithAsynchronousLoadingofData

Step3:ImplementingonCreateLoader()

Step4:ImplementingonLoadFinished()

Step5:ImplementingonLoaderReset()

UsingSearchwithLoaders

UnderstandingtheOrderofLoaderManagerCallbacks

WritingCustomLoaders

Resources

Summary

Chapter27:ExploringtheContactsAPI

UnderstandingAccountsEnumeratingAccounts

UnderstandingContactsExaminingtheContactsSQLiteDatabase

UnderstandingRawContacts

UnderstandingtheContactsDataTable

UnderstandingAggregatedContacts

Exploringview_contacts

Exploringcontact_entities_view

WorkingwiththeContactsAPIExploringAccounts

ExploringAggregatedContacts

ExploringRawContacts

ExploringRawContactData

AddingaContactwithItsDetails

ControllingAggregationofContacts

UnderstandingPersonalProfileReadingProfileRawContacts

ReadingProfileContactData

AddingDatatothePersonalProfile

RoleofSyncAdapters

UsingBatchOperationstoOptimizeContentProviderUpdatesIdeaofBatchingContentProviderUpdates

BatchingCommitsbyYielding

UsingBackReferences

OptimisticLocking

ReusingtheContactProviderUI

UsingGroupFeatures

UsingPhotoFeatures

References

Summary

Chapter28:ExploringSecurityandPermissions

UnderstandingtheAndroidSecurityModelOverviewofSecurityConcepts

SigningApplicationsforDeployment

PerformingRuntimeSecurityChecksUnderstandingSecurityattheProcessBoundary

DeclaringandUsingPermissions

UnderstandingandUsingURIPermissions

References

Summary

Chapter29:UsingGoogleCloudMessagingwithAndroid

WhatIsGoogleCloudMessaging?UnderstandingtheKeyBuildingBlocksofGCM

PreparingtoUseGCMinYourApplication

AuthenticatingGCMCommunication

BuildinganAndroidGCM-EnabledApplicationCodingtheClientComponentforGCM

CodingtheServerComponentforGCM

MovingBeyondtheGCMIntroduction

Chapter30:DeployingYourApplication:GooglePlayStoreandBeyond

BecomingaPublisherFollowingtheRules

DeveloperConsole

PreparingYourApplicationforSaleTestingforDifferentDevices

SupportingDifferentScreenSizes

PreparingAndroidManifest.xmlforUploading

LocalizingYourApplication

PreparingYourApplicationIcon

DirectingUsersBacktothePlayStore

TheAndroidLicensingService

UsingProGuardforOptimization,FightingPiracy

PreparingYour.apkFileforUploading

UploadingYourApplicationGraphics

ListingDetails

PublishingOptions

ContactInformation

Consent

UserExperienceonGooglePlayStore

BeyondGooglePlayStore

References

Summary

Index

AbouttheAuthorsDaveMacLeanisasoftwareengineerandarchitectlivingandworkinginOrlando,Florida.Since1980,hehasprogrammedinmanylanguages,developingsolutionsrangingfromrobotautomationsystemstodatawarehousing,fromwebself-serviceapplicationstoelectronicdatainterchangetransactionprocessors.DavehasworkedforSunMicrosystems,IBM,TrimbleNavigation,GeneralMotors,BlueCrossBlueShieldofFlorida,andseveralsmallcompanies.HehaswrittenseveralbooksonAndroidandafewmagazinearticles.HegraduatedfromtheUniversityofWaterlooinCanadawithaSystemsDesignEngineeringdegree.Visithisblogathttp://davemac327.blogspot.comorcontacthimatdavemac327@gmail.com.

SatyaKomatinenihasbeenprogrammingformorethan20yearsintheITandWebspace.HehashadtheopportunitytoworkwithAssembly,C,C++,Rexx,Java,C#,Lisp,HTML,JavaScript,CSS,SVG,relationaldatabases,objectdatabases,andrelatedtechnologies.Hehaspublishedmorethan30articlestouchingmanyoftheseareas,bothinprintandonline.HehasbeenafrequentspeakeratO’ReillyOpenSourceConference,speakingoninnovationsaroundJavaandWeb.SatyahasdoneaconsiderableamountoforiginalworkincreatingAspire,acomprehensiveopen-sourceJava-basedwebframework,andhasexploredpersonalwebproductivityandcollaborationtoolsthroughhisopen-sourceworkforKnowledgeFolders.com.Satyaholdsamaster’sdegreeinelectricalengineeringfromIndianInstituteofTechnologyandabachelor’sdegreeinelectricalengineeringfromAndhraUniversity,India.YoucanfindhiswebsiteatSatyaKomatineni.com.

GrantAllenhasworkedintheITfieldforover20years,asaCTO,enterprisearchitect,anddatabaseadministrator.Grant’sroleshavecoveredprivateenterprise,academia,andthegovernmentsectoraroundtheworld,specializinginglobal-scalesystemsdesign,development,andperformance.Heisafrequentspeakeratindustryandacademicconferences,ontopicsrangingfromdataminingtocompliance,andtechnologiessuchasdatabases(DB2,Oracle,SQLServer,MySQL),contentmanagement,collaboration,disruptiveinnovation,andmobileecosystemslikeAndroid.HisfirstAndroidapplicationwasatasklisttoremindhimtofinishallhisotherunfinishedAndroidprojects.GrantworksforGoogle,andinhissparetimeiscompletingaPhDonbuildinginnovativehigh-technologyenvironments.GrantistheauthorofBeginningAndroidandleadauthorofOracleSQLRecipesandTheDefinitiveGuidetoSQLite.

AbouttheTechincalreviewerShaneKirkearnedaB.S.inComputerSciencefromtheUniversityofKentuckyin2000.He’scurrentlyaSeniorSoftwareEngineerforIDEXXLaboratoriesinWestbrook,Maine,wherehespendshisdaysworkingoncommunicationsolutionsforembeddedsystems.Shane’sforayintomobiledevelopmentbeganin2010,shortlyafterpurchasinghisfirstsmartphone—aDroidXrunningEclair(Android2.1).He’sbeenhookedonAndroideversince.

AcknowledgmentsWritingthisbooktookeffortnotonlyonthepartoftheauthors,butalsofromsomeoftheverytalentedstaffatApress,aswellasthetechnicalreviewer.Therefore,wewouldliketothankSteveAnglin,MatthewMoodie,DouglasPundick,MarkPowers,BrendanFrost,AnaPanchoo,andJillBalzano.

Wewouldalsoliketoextendourdeepestappreciationtothetechnicalreviewer—ShaneKirk—forhisexpertappraisalsandattentiontodetail.Thisbookissomuchbetterbecauseofhisefforts.

Writingatechnicalbookaboutasubjectthatfrequentlychangesisadauntingtask.Whenthedocumentationdidn’tsay,andthesourcecodedidn’treveal,wewouldsearchtheInternetforanswers.Andwe’deventuallyfindwhatwewerelookingfor,buriedhereandthere.ToalltheotherAndroiddevelopersouttherewhoareworkingalongwithustoprovideanswers,wethankyou.

Finally,theauthorsaredeeplygratefultotheirfamiliesforlettingustoilawayonnights,earlymornings,andweekends.Ittakesgreatdedicationtowriteabook,andperhapsevenmoretoputupwithauthorswhiletheywrite.

ForewordWaybackin2008,IwasgivenmyfirstAndroiddevice.ItwastheDream,alsoknownastheG1,andIimmediatelystartedtinkeringwithit.Afterall,herewasasmartphonewiththepromiseofthousandsofapplications,andwhoknewhowmanyhundredsofpossiblehandsets.ThatshowshowmuchIknewatthetime!Ireallyshouldhavebeenthinkingintheorderofmillionsofapplications,andtensofthousandsofdevices,becausethatiswhereAndroidisheadingtoday.

Whetheritistraditionalphones,tablets,cars,in-flightentertainmentsystems,robots,oranyotherofthemyriadAndroiddevicesoutthere,whatmakesthemgreataretheapplicationswrittenbypeoplelikeyou,dearreader!Everyday,Androiddeveloperspushthepossibilitiesofwhatapplications—andAndroid—cando,anditisthatenergythatdrawsmetothecommunity,andtohelpinginmyownsmallwaywithbookslikeProAndroid.

OneofthebestobservationsabouttechnologyandinnovationIhaveheardisthatinnovationhappenswhenyoucreatesomethingandshareitwithanotherperson,whichtheythenadaptanduseinatotallyunexpectedway.Soletmecommendthisbooktoyouinthatspirit.EnjoyeverythingProAndroidhastoofferyou,andtakeittocreatesomethingtotallyunexpected!We’llbefirstinlinetotryitout,whateveritis.

—GrantAllenNewYorkMay2015

IntroductionWelcometothewonderfulworldofAndroid.Aworldwhere,withabitofknowledgeandeffort,youtoocanwriteAndroidapplications.Towritegoodapplications,however,youwillneedtodigdeeper,tounderstandthefundamentalsoftheAndroidarchitecture,tounderstandhowapplicationscanworktogether,tounderstandhowmobileapplicationsaredifferentfromallpreviousformsofprogramming.TheonlinedocumentationonAndroidisfair,butitdoesnotgofarenough.Youcanreadthesourcecode,butthat’snotatalleasy.

Thisbookistheculminationofsevenyearsofresearching,developing,testing,refining,andwritingaboutAndroid.We’vereadalltheonlinedocumentation,scouredthroughsourcecode,exploredthefarreachesoftheInternet,andhavecompiledthisbook.We’vefilledinthegaps,anticipatedthequestionsyouhave,andprovidedanswers.Alongthewaywe’veseenAPIscomeandgoandberevised.We’veseenmajorchangesinhowapplicationsareconstructed.AtfirstweallusedActivities,butwhentabletscamealongwestartedusingFragments.We’vetakeneverythingwe’velearnedandfilledthisbookwithpracticalguidancetousingthelatestAndroidAPIstowriteinterestingapplications.

Youwillstillfindcoverageofthebeginningtopics,tohelpthenewlearnergetstarteddevelopingforAndroid.Youwillalsofindcoverageofthemoreadvancedtopics,suchasGoogleMapsAndroidAPIv2,whichisverydifferentfromv1.We’veupdatedthiseditionwiththelatestinformationontheavailableAPIs.Youwillfindin-depthcoverageofintents,services,broadcastreceivers,communication,fragments,widgets,sensors,animation,security,GoogleCloudMessaging,audioandvideo,andmore.AndforeverytopictherearesampleprogramsthatillustrateeachAPIinmeaningfulways.Allsourcecodeisdownloadable,soyoucancopyandpasteitintoyourapplicationstogetagreatheadstart.

Chapter1

HelloAndroidWelcometothebook,andwelcometotheworldofAndroiddevelopment.Inalittleundertenyears,Androidhashelpedchangethefaceofmodernmobilecomputingandtelephonyandlaunchedarevolutioninhowapplicationsaredeveloped,andbywhom.Withthisbookinyourhands,youarenowpartofthegreatAndroidexplosion!We’regoingtoassumethatyouwanttogetstraightatworkingwithAndroid,sowe'renotgoingtoboreyouwithafiresidechataboutAndroid'shistory,majorcharacters,plaudits,oranyotherprose.We'regoingtogetstraighttoit!

Inthischapter,you’llstartbyseeingwhatyouneedtobeginbuildingapplicationswiththeAndroidsoftwaredevelopmentkit(SDK)andsetupyourchoiceofdevelopmentenvironment.Next,youstepthrougha“HelloWorld!”application.ThenthechapterexplainstheAndroidapplicationlifecycleandendswithadiscussionaboutrunningyourapplicationswithAndroidVirtualDevices(AVDs)andonrealdevices.Solet’sgetstarted.

PrerequisitesforAndroidDevelopmentTobuildapplicationsforAndroid,youneedtheJavaSEDevelopmentKit(JDK),theAndroidSDK,andadevelopmentenvironment.Strictlyspeaking,youcandevelopyourapplicationsusingnothingmorethanaprimitivetexteditorandahandfulofcommand-linetoolslikeAnt.Forthepurposesofthisbook,we’llusethecommonlyavailableEclipseIDE,thoughyouarefreetoadoptAndroidStudioanditsIntelliJunderpinnings—we’llevenwalkthroughAndroidStudioforthosewhohavenotseenit.Withtheexceptionofafewadd-ontools,theexamplesweshareinthebookwillworkequallywellbetweenthesetwoIDEs.

TheAndroidSDKrequiresJDK6or7(thefullJDK,notjusttheJavaRuntimeEnvironment[JRE])andoptionallyasupportedIDE.Currently,GoogledirectlysupportstwoalternativeIDEs,providingsomechoice.Historically,EclipsewasthefirstIDEsupportedbyGoogleforAndroiddevelopment,anddevelopingforAndroid4.4KitKator5.0LollipoprequiresEclipse3.6.2orhigher(thisbookusesEclipse4.2or4.4,alsoknownasJunoandLuna,respectively,andotherversions).ThealternativeenvironmentreleasedandsupportedbyGoogleforAndroidisnowknownasAndroidStudio.ThisisapackagedversionofIDEAIntelliJwithbuilt-inAndroidSDKanddevelopertools.

NoteAtthetimeofthiswriting,Java8wasavailablebutnotyetsupportedbytheAndroidSDK.InpreviousversionsoftheAndroidSDK,Java5wasalsosupported,butthisisnolongerthecase.ThelatestversionofEclipse(4.4,a.k.a.Juno)wasalsoavailable,butAndroidhashistoricallynotbeenreliableonthelatestEclipserightaway.Checkthesystemrequirementsheretofindthelatest:http://developer.android.com/sdk/index.html.

TheAndroidSDKiscompatiblewithWindows(WindowsXP,WindowsVista,andWindows7),MacOSX(Intelonly),andLinux(Intelonly).Intermsofhardware,youneedanIntelmachine,themorepowerfulthebetter.

Tomakeyourlifeeasier,ifyouchooseEclipseasyourIDE,youwillwanttouseAndroiddevelopmenttools(ADT).ADTisanEclipseplug-inthatsupportsbuildingAndroidapplicationswiththeEclipseIDE.

TheAndroidSDKismadeupoftwomainparts:thetoolsandthepackages.WhenyoufirstinstalltheSDK,allyougetarethebasetools.Theseareexecutablesandsupportingfilestohelpyoudevelopapplications.ThepackagesarethefilesspecifictoaparticularversionofAndroid(calledaplatform)oraparticularadd-ontoaplatform.TheplatformsincludeAndroid1.5through4.4.2.Theadd-onsincludetheGoogleMapsAPI,theMarketLicenseValidator,andevenvendor-suppliedonessuchasSamsung’sGalaxyTabadd-on.AfteryouinstalltheSDK,youthenuseoneofthetoolstodownloadandsetuptheplatformsandadd-ons.

Remember,youonlyneedtosetupandconfigureoneofEclipseorAndroidStudio.Youcanusebothifyouaresoinclined,butit’scertainlynotrequired.Let’sgetstarted!

SettingUpYourEclipseEnvironmentInthissection,youwalkthroughdownloadingJDK6,theEclipseIDE,theAndroidSDK(toolsandpackages),andADT.YoualsoconfigureEclipsetobuildAndroidapplications.Googleprovidesapagetodescribetheinstallationprocess(http://developer.android.com/sdk/installing.html)butleavesoutsomecrucialsteps,asyouwillsee.

DownloadingJDKThefirstthingyouneedistheJDK.TheAndroidSDKrequiresJDK6orhigher;we’vedevelopedourexamplesusingJDK6and7,dependingontheversionofEclipseorAndroidStudioinuse.ForWindowsandMacOSX,downloadJDK7fromtheOraclewebsite(www.oracle.com/technetwork/java/javase/downloads/index.html)andinstallit.YouonlyneedtheJDK,notthebundles.ToinstalltheJDKforLinux,openaTerminalwindowandinstructyourpackagemanagertoinstallit.Forexample,inDebianorUbuntutrythefollowing:

sudoapt-getinstallsun-java7-jdk

ThisshouldinstalltheJDKplusanydependenciessuchastheJRE.Ifitdoesn’t,itprobablymeansyouneedtoaddanewsoftwaresourceandthentrythatcommandagain.Thewebpagehttps://help.ubuntu.com/community/Repositories/Ubuntuexplainssoftwaresourcesandhowtoaddtheconnectiontothird-partysoftware.TheprocessisdifferentdependingonwhichversionofLinuxyouhave.Afteryou’vedonethat,retrythecommand.

WiththeintroductionofUbuntu10.04(LucidLynx),UbunturecommendsusingOpenJDKinsteadoftheOracle/SunJDK.ToinstallOpenJDK,trythefollowing:

sudoapt-getinstallopenjdk-7-jdk

Ifthisisnotfound,setupthethird-partysoftwareasoutlinedpreviouslyandrunthecommandagain.AllpackagesonwhichtheJDKdependsareautomaticallyaddedforyou.ItispossibletohavebothOpenJDKandtheOracle/SunJDKinstalledatthesametime.ToswitchactiveJavabetweentheinstalledversionsofJavaonUbuntu,runthiscommandatashellprompt

sudoupdate-alternatives--configjava

andthenchoosewhichJavayouwantasthedefault.

NowthatyouhaveaJavaJDKinstalled,it’stimetosettheJAVA_HOMEenvironmentvariabletopointtotheJDKinstallfolder.TodothisonaWindowsXPmachine,chooseStart MyComputer,right-click,selectProperties,choosetheAdvancedtab,andclickEnvironmentVariables.ClickNewtoaddthevariableorEdittomodifyitifitalreadyexists.ThevalueofJAVA_HOMEissomethinglikeC:\ProgramFiles\Java\jdk1.7.0_79.

ForWindowsVistaandWindows7,thestepstogettotheEnvironmentVariablesscreenarealittledifferent.ChooseStart Computer,right-click,chooseProperties,clickthelinkforAdvancedSystemSettings,andclickEnvironmentVariables.Afterthat,followthesameinstructionsasforWindowsXPtochangetheJAVA_HOMEenvironmentvariable.

ForMacOSX,yousetJAVA_HOMEinthe.bashrcfileinyourhomedirectory.Editorcreatethe.bashrcfile,andaddalinethatlookslikethis

exportJAVA_HOME=path_to_JDK_directory

wherepath_to_JDK_directoryisprobably/Library/Java/Home.ForLinux,edityour.bashrcfileandaddalineliketheoneforMacOSX,exceptthatyourpathtoJavaisprobablysomethinglike/usr/lib/jvm/java-6-sunor/usr/lib/jvm/java-6-openjdk.

DownloadingEclipseAftertheJDKisinstalled,youcandownloadtheEclipseIDEforJavaDevelopers.(Youdon’tneedtheeditionforJavaEE;itworks,butit’smuchlargerandincludesthingsyoudon’tneedforthisbook.)TheexamplesinthisbookuseEclipse4.2or4.4(onbothLinuxandWindowsenvironments).YoucandownloadallversionsofEclipsefromwww.eclipse.org/downloads/.

NoteAsanalternativetotheindividualstepspresentedhere,youcanalsodownloadtheADTBundlefromtheAndroiddevelopersite.ThisincludesEclipsewithbuilt-indevelopertoolsandtheAndroidSDKinonepackage.It’s

agreatwaytogetstartedquickly,butifyouhaveanexistingenvironment,orjustwanttoknowhowallthecomponentsarestitchedtogether,thenfollowingthestep-by-stepinstructionsisthewaytogo.

TheEclipsedistributionisa.zipfilethatcanbeextractedjustaboutanywhere.ThesimplestplacetoextracttoonWindowsisC:\,whichresultsinaC:\eclipsefolderwhereyoufindeclipse.exe.Dependingonyoursecurityconfiguration,WindowsmayinsistonenforcingUACwhenrunningfromC:.ForMacOSX,youcanextracttoApplications.ForLinux,youcanextracttoyourhomedirectoryorhaveyouradministratorputEclipseintoacommonplacewhereyoucangettoit.TheEclipseexecutableisintheeclipsefolderforallplatforms.YoumayalsofindandinstallEclipseusingLinux’sSoftwareCenterforaddingnewapplications,althoughthismaynotprovideyouwiththelatestversion.

WhenyoufirststartupEclipse,itasksyouforalocationfortheworkspace.Tomakethingseasy,youcanchooseasimplelocationsuchasC:\androidoradirectoryunderyourhomedirectory.Ifyousharethecomputerwithothers,youshouldputyourworkspacefoldersomewhereunderneathyourhomedirectory.

DownloadingtheAndroidSDKTobuildapplicationsforAndroid,youneedtheAndroidSDK.Asstatedbefore,theSDKcomeswiththebasetools;thenyoudownloadthepackagepartsthatyouneedand/orwanttouse.ThetoolspartoftheSDKincludesanemulatorsoyoudon’tneedamobiledevicewiththeAndroidOStodevelopAndroidapplications.Italsohasasetuputilitytoallowyoutoinstallthepackagesthatyouwanttodownload.

YoucandownloadtheAndroidSDKfromhttp://developer.android.com/sdk.Itshipsasa.zipfile,similartothewayEclipseisdistributed,soyouneedtounzipittoanappropriatelocation.ForWindows,unzipthefiletoaconvenientlocation(weusedtheC:drive),afterwhichyoushouldhaveafoldercalledsomethinglikeC:\android-sdk-windowsthatcontainsthefilesasshowninFigure1-1.ForMacOSXandLinux,youcanunzipthefiletoyourhomedirectory.NoticethatMacOSXandLinuxdonothaveanSDKManagerexecutable;theequivalentoftheSDKManagerinMacOSXandLinuxistorunthetools/androidprogram.

Figure1-1.BasecontentsoftheAndroidSDK

Analternativeapproach(forWindowsonly)istodownloadaninstallerEXEinsteadofthezipfileandthenruntheinstallerexecutable.ThisexecutablechecksfortheJavaJDK,unpackstheembeddedfilesforyou,andrunstheSDKManagerprogramtohelpyousetuptherestofthedownloads.

WhetherthroughusingtheWindowsinstallerorbyexecutingtheSDKManager,youshouldinstallsomepackagesnext.WhenyoufirstinstalltheAndroidSDK,itdoesnotcomewithanyplatformversions(thatis,versionsofAndroid).Installingplatformsisprettyeasy.Afteryou’velaunchedtheSDKManager,youseewhatisinstalledandwhat’savailabletoinstall,asshowninFigure1-2.YoumustaddAndroidSDKtoolsandplatform-toolsinorderforyourenvironmenttowork.Becauseyouuseitshortly,addatleasttheAndroid1.6SDKplatform,aswellasthelatestplatformshowninyourinstaller.

Figure1-2.AddingpackagestotheAndroidSDK

ClicktheInstallbutton.YouneedtoclickAcceptforeachitemyou’reinstalling(orAcceptAll)andthenclickInstall.Androidthendownloadsyourpackagesandplatformstomakethemavailabletoyou.TheGoogleAPIsareadd-onsfordevelopingapplicationsusingGoogleMaps.Youcanalwayscomebacktoaddmorepackageslater.

UpdatingYourPATHEnvironmentVariableTheAndroidSDKcomeswithatoolsdirectorythatyouwanttohaveinyourPATH.

YoualsoneedinyourPATHtheplatform-toolsdirectoryyoujustinstalled.Let’saddthemnowor,ifyou’reupgrading,makesurethey’recorrect.Whileyou’rethere,youcanalsoaddaJDKbindirectory,whichwillmakelifeeasierlater.

ForWindows,getbacktotheEnvironmentVariableswindow.EditthePATHvariableandaddasemicolon(;)ontheend,followedbythepathtotheAndroidSDKtoolsfolder,followedbyanothersemicolon,followedbythepathtotheAndroidSDKplatform-toolsfolder,followedbyanothersemicolon,andthen%JAVA_HOME%\bin.ClickOKwhenyou’redone.ForMacOSXandLinux,edityour.bashrcfileandaddtheAndroidSDKtoolsdirectorypathtoyourPATHvariable,aswellastheAndroidSDKplatform-toolsdirectoryandthe$JAVA_HOME/bindirectory.SomethinglikethefollowingworksforLinux:

exportPATH=$PATH:$HOME/android-sdk-linux_x86/tools:$HOME/android-sdk-linux_x86/platform-tools:$JAVA_HOME/bin

JustmakesurethatthePATHcomponentthat’spointingtotheAndroidSDKtoolsdirectoriesiscorrectforyourparticularsetup.

TheToolsWindowLaterinthisbook,therearetimeswhenyouneedtoexecuteacommand-lineutilityprogram.TheseprogramsarepartoftheJDKorpartoftheAndroidSDK.ByhavingthesedirectoriesinyourPATH,youdon’tneedtospecifythefullpathnamesinordertoexecutethem,butyouneedtostartupatoolswindowinordertorunthem(laterchaptersrefertothistoolswindow).TheeasiestwaytocreateatoolswindowinWindowsistochooseStart Run,typeincmd,andclickOK.ForMacOSX,chooseTerminalfromyourApplicationsfolderinFinderorfromtheDockifit’sthere.ForLinux,runyourfavoriteterminal.

YoumayneedtoknowtheIPaddressofyourworkstationlater.TofindthisinWindows,launchatoolswindowandenterthecommandipconfig.TheresultscontainanentryforIPv4(orsomethinglikethat)withyourIPaddresslistednexttoit.AnIPaddresslookssomethinglikethis:192.168.1.25.ForMacOSXandLinux,launchatoolswindowandusethecommandifconfig.YoufindyourIPaddressnexttothelabelinetaddr.

Youmayseeanetworkconnectioncalledlocalhostorlo;theIPaddressforthisnetworkconnectionis127.0.0.1.Thisisaspecialnetworkconnectionusedbytheoperatingsystemandisnotthesameasyourworkstation’sIPaddress.Lookforadifferentnumberforyourworkstation’sIPaddress.

InstallingADTNowyouneedtoinstallADT(veryrecentlyrenamedtoGDT,theGoogleDeveloperTools),anEclipseplug-inthathelpsyoubuildAndroidapplications.Specifically,ADTintegrateswithEclipsetoprovidefacilitiesforyoutocreate,test,anddebugAndroid

applications.YouneedtousetheInstallNewSoftwarefacilityinEclipsetoperformtheinstallation.(TheinstructionsforupgradingADTappearlaterinthissection.)Togetstarted,launchtheEclipseIDEandfollowthesesteps:

1. SelectHelp InstallNewSoftware.

2. SelecttheWorkWithfield,typein

https://dl-ssl.google.com/android/eclipse/,

andpressEnter.EclipsecontactsthesiteandpopulatesthelistasshowninFigure1-3.

Figure1-3.InstallingADTusingtheInstallNewSoftwarefeatureinEclipse

3. YoushouldseeanentrynamedDeveloperToolswithfourchildnodes:AndroidDDMS,AndroidDevelopmentTools,AndroidHierarchyViewer,andAndroidTraceview.Justbeforepublishingthisbook,GoogleupdatedtheADTtobepartofthemoregenericGoogleDeveloperToolspluginforEclipse,orGDT.LookforthesameoptionsintheGDT.SelecttheparentnodeDeveloperTools,makesurethechildnodesarealsoselected,andclicktheNextbutton.Theversionsyouseemaybenewerthanthese,andthat’sokay.Youmayalsoseeadditionaltools.Thesetoolsareexplained

furtherinChapter11.

4. Eclipseasksyoutoverifythetoolstoinstall.ClickNext.

5. You’reaskedtoreviewthelicensesforADTaswellasforthetoolsrequiredtoinstallADT.Reviewthelicenses,click“Iaccept,”andthenclicktheFinishbutton.

Eclipsedownloadsthedevelopertoolsandinstallsthem.YouneedtorestartEclipseforthenewplug-intoshowupintheIDE.

IfyoualreadyhaveanolderversionofADTinEclipse,gototheEclipseHelpmenuandchooseCheckforUpdates.YoushouldseethenewversionofADTandbeabletofollowtheinstallationinstructions,pickingupatstep3.

NoteIfyou’redoinganupgradeofADT,youmaynotseesomeofthesetoolsinthelistoftoolstobeupgraded.Ifyoudon’tseethem,thenafteryou’veupgradedtherestoftheADT,gotoInstallNewSoftwareandselecthttps://dl-ssl.google.com/android/eclipse/fromtheWorksWithmenu.Themiddlewindowshouldshowyouothertoolsthatareavailabletobeinstalled.

ThefinalsteptomakeADTfunctionalinEclipseistopointittotheAndroidSDK.InEclipse,selectWindow Preferences.(OnMacOSX,PreferencesisundertheEclipsemenu.)InthePreferencesdialogbox,selecttheAndroidnodeandsettheSDKLocationfieldtothepathoftheAndroidSDK(seeFigure1-4)andthenclicktheApplybutton.NotethatyoumayseeadialogboxaskingifyouwanttosendusagestatisticstoGoogleconcerningtheAndroidSDK;thatdecisionisuptoyou.

Figure1-4.PointingADTtotheAndroidSDK

YoumaywanttomakeonemorePreferenceschangeontheAndroid Buildpage.TheSkipPackagingoptionshouldbecheckedifyou’dliketomakeyourfilesavesfaster.Bydefault,theADTreadiesyourapplicationforlauncheverytimeitbuildsit.Bycheckingthisoption,packagingandindexingoccuronlywhentrulyneeded.

FromEclipse,youcanlaunchtheSDKManager.Todoso,chooseWindow AndroidSDKManager.YoushouldseethesamewindowasinFigure1-2.

Ifyou’vechosenEclipseasyourIDE,youarealmostreadyforyourfirstAndroidapplication—youcanskipthefollowingsectiononAndroidStudioandheadstraighttothe“LearningAndroid’sFundamentalComponents”section.

SettingUpYourAndroidStudioEnvironmentIn2013,Googleintroducedasecondsupporteddevelopmentenvironment,knownasAndroidStudio(orAndroidDeveloperStudioatthetimeoflaunch).ThisisbasedaroundapopularJavaIDE:IDEAIntelliJ.ThemostimportantthingtoknowaboutAndroidStudioisthatitisstillaworkinprogress.Asofthisbook’swriting,thelatestversionis1.2.Anyonefamiliarwiththevagariesofversionnumbersknowsthatstartingwithalow

numberusuallymeans“beware!”

ThesecondmostimportantthingtorememberisthatAndroidStudiocurrentlyassumesa64-bitdevelopmentenvironment.ThatmeansdependencieslikeJavaalsoneedtobe64-bit.

ThenextsectionsbrieflycoverthesetupofAndroidStudioforthoseinterestedorgung-hoenoughtouseit.BemindfulthattherestofthebookpredominantlyshowsexamplesandoptionsusingEclipse.

JavarequirementsforAndroidStudioLikeEclipse,AndroidStudioreliesonaworkingJavainstallation.AndroidStudiowillattempttoautomaticallydiscoveryourJavaenvironmentduringinstallation,soitpaystohaveJavainstalledandconfigured.

ForJavainstallation,rememberthatAndroidStudiois64-bit.Inallotherrespects,youcanfollowtheprecedingsectiontitled“DownloadingJDK”—wewon’trepeatthatword-for-wordheretosavesometrees.Ensureyoufollowalltheinstructionsthere,includingsettingtheJAVA_HOMEenvironmentvariable,asthisisthemainindicatorusedbytheAndroidStudioinstallertofindyourJavainstallation.

DownloadingandInstallingAndroidStudioGooglemakesAndroidStudioavailablefromthemainAndroiddevelopmentsite,currentlyattheURLhttp://developer.android.com/sdk/installing/studio.html.Thatmaychangeatanytime,butaquicksearchonthedeveloper.android.comsiteshouldfindit.AndroidStudioispackagedasamonolithicbundle,withnearlyallthecomponentsyouneed.TheJavaSDKistheexception—we’llcoverthatshortly.ThepackagedownloadedfromtheprecedingURLwillbenamedsomethinglikeandroid-studio-bundle-132.893413-windows.exeforwindows,orasimilarnamewithadifferentextensionforOSXandLinux,andincludesthefollowing:

CurrentlatestbuildoftheAndroidStudiobundleofIntelliJIDEA

Built-inAndroidSDK

AllrelatedAndroidbuildtools

AndroidVirtualDeviceimages

We’lltalkmoreaboutthesecomponentsinlaterchapters.ForaWindowsinstallationruntheexecutableandfollowthepromptstochooseaninstallationpath,anddecidewhetherAndroidStudioismadeavailabletoallusersontheWindowsmachine,orjustthecurrentuser.ForOSX,openthe.dmgfileandcopytheAndroidStudioentrytoyourApplicationsfolder.UnderLinux,extractthecontentsofthe.tgzfiletoyourdesiredlocation.

Onceinstalled,youcanstartAndroidStudiounderWindowsfromthestartmenufolder

youchosewhenprompted;underOSXfromtheApplicationsfolder;andunderLinuxbyrunningthe./android-studio/bin/studio.shfileunderyourinstallationdirectory.Whatevertheoperatingsystem,youshouldseetheAndroidStudiohomescreenasdepictedinFigure1-5.

Figure1-5.AndroidStudiowhenfirstlaunched

LearningAndroid’sFundamentalComponentsEveryapplicationframeworkhassomekeycomponentsthatdevelopersneedtounderstandbeforetheycanbegintowriteapplicationsbasedontheframework.Forexample,youneedtounderstandJavaServerPages(JSP)andservletsinordertowriteJava2Platform,EnterpriseEdition(J2EE)applications.Similarly,youneedtounderstandviews,activities,fragments,intents,contentproviders,services,andtheAndroidManifest.xmlfilewhenyoubuildapplicationsforAndroid.Youbrieflycoverthesefundamentalconceptshereandexploretheminmoredetailthroughoutthebook.

ViewViewsareuserinterface(UI)elementsthatformthebasicbuildingblocksofauserinterface.Aviewcanbeabutton,alabel,atextfield,ormanyotherUIelements.Ifyou’refamiliarwithviewsinJ2EEandSwing,thenyouunderstandviewsinAndroid.Viewsarealsousedascontainersforviews,whichmeansthere’susuallyahierarchyofviewsintheUI.Intheend,everythingyouseeisaview.

ActivityAnactivityisaUIconceptthatusuallyrepresentsasinglescreeninyourapplication.Itgenerallycontainsoneormoreviews,butitdoesn’thaveto.Anactivityisprettymuchlikeitsounds—somethingthathelpstheuserdoonething,whichcouldbeviewingdata,creatingdata,oreditingdata.MostAndroidapplicationshaveseveralactivitieswithinthem.

FragmentWhenascreenislarge,itbecomesdifficulttomanageallofitsfunctionalityinasingleactivity.Fragmentsarelikesub-activities,andanactivitycandisplayoneormorefragmentsonthescreenatthesametime.Whenascreenissmall,anactivityismorelikelytocontainjustonefragment,andthatfragmentcanbethesameoneusedwithinlargerscreens.

IntentAnintentgenericallydefinesan“intention”todosomework.Intentsencapsulateseveralconcepts,sothebestapproachtounderstandingthemistoseeexamplesoftheiruse.Youcanuseintentstoperformthefollowingtasks:

Broadcastamessage

Startaservice

Launchanactivity

Displayawebpageoralistofcontacts

Dialaphonenumberoransweraphonecall

Intentsarenotalwaysinitiatedbyyourapplication—they’realsousedbythesystemtonotifyyourapplicationofspecificevents(suchasthearrivalofatextmessage).

Intentscanbeexplicitorimplicit.IfyousimplysaythatyouwanttodisplayaURL,thesystemdecideswhatcomponentwillfulfilltheintention.Youcanalsoprovidespecificinformationaboutwhatshouldhandletheintention.Intentslooselycoupletheactionandactionhandler.

ContentProviderDatasharingamongmobileapplicationsonadeviceiscommon.Therefore,Androiddefinesastandardmechanismforapplicationstosharedata(suchasalistofcontacts)withoutexposingtheunderlyingstorage,structure,andimplementation.Throughcontentproviders,youcanexposeyourdataandhaveyourapplicationsusedatafromotherapplications.

ServiceServicesinAndroidresembleservicesyouseeinWindowsorotherplatforms—they’rebackgroundprocessesthatcanpotentiallyrunforalongtime.Androiddefinestwotypesofservices:localservicesandremoteservices.Localservicesarecomponentsthatareonlyaccessiblebytheapplicationthatishostingtheservice.Conversely,remoteservicesareservicesthataremeanttobeaccessedremotelybyotherapplicationsrunningonthedevice.

Anexampleofaserviceisacomponentthatisusedbyane-mailapplicationtopollfornewmessages.Thiskindofservicemaybealocalserviceiftheserviceisnotusedbyotherapplicationsrunningonthedevice.Ifseveralapplicationsusetheservice,thenit’simplementedasaremoteservice.

AndroidManifest.xmlAndroidManifest.xml,whichissimilartotheweb.xmlfileintheJ2EEworld,definesthecontentsandbehaviorofyourapplication.Forexample,itlistsyourapplication’sactivitiesandservices,alongwiththepermissionsandfeaturestheapplicationneedstorun.

AVDsAnAVDallowsdeveloperstotesttheirapplicationswithouthookingupanactualAndroiddevice(typicallyaphoneoratablet).AVDscanbecreatedinvariousconfigurationstoemulatedifferenttypesofrealdevices.

HelloWorld!Nowyou’rereadytobuildyourfirstAndroidapplication.Youstartbybuildingasimple“HelloWorld!”program.Createtheskeletonoftheapplicationbyfollowingthesesteps:

1. LaunchEclipse,andselectFile New Project.IntheNewProjectdialogbox,selectAndroidApplicationProjectandthenclickNext.YouseetheNewAndroidProjectdialogbox,asshowninFigure1-6.(EclipsemayhaveaddedAndroidProjecttotheNewmenu,soyoucanuseitifit’sthere.)There’salsoaNewAndroid

Projectbuttononthetoolbar.

Figure1-6.UsingtheNewProjectWizardtocreateanAndroidapplication

2. AsshowninFigure1-6,enterHelloAndroidastheprojectname.YouneedtodistinguishthisprojectfromotherprojectsyoucreateinEclipse,sochooseanamethatwillmakesensetoyouwhenyouarelookingatalltheprojectsinyourEclipseenvironment.YouwillalsoseetheavailableBuildTargets.SelectAndroid2.2.ThisistheversionofAndroidyouuseasyourbasefortheapplication.YoucanrunyourapplicationonlaterversionsofAndroid,suchas4.3and4.4;butAndroid2.2hasallthefunctionalityyouneedforthisexample,sochooseitasyourtarget.Ingeneral,it’sbesttochoosethelowestversionnumberyoucan,becausethatmaximizesthenumberofdevicesthatcanrunyourapplication.

3. LeavetheProjectNametoauto-completeitselfbasedonyourApplicationName.

4. Usecom.androidbook.helloasthepackagename.LikeallJavaapplications,yourapplicationmusthaveabasepackagename,andthisisit.Thispackagenamewillbeusedasanidentifierforyourapplicationandmustbeuniqueacrossallapplications.Forthisreason,it’sbesttostartthepackagenamewithadomainnamethatyouown.Ifyoudon’townone,becreativetoensurethatyourpackagenamewon’tlikelybeusedbyanyoneelse.ClickNext.

5. Thenextwindowprovidesoptionsforcustomerlaunchericons,theactualdirectoryfortheworkspaceinwhichyousourcecodeandotherfilesarestored,andseveralotheroptions.Leavealloftheseatthedefault,andclickNext.

6. ThenextwindowshowsyoutheConfigureLauncherIconoptionsandsettings,asshowninFigure1-7.Feelfreetoplaywiththeoptionshere,thoughanychangesyoumakearecosmeticandaffectthelookofthelaunchericonwhenyourapplicationisdeployed,andnotitsactuallogic.ClickNextwhenready.

Figure1-7.TheAndroidlauncherconfigurationoptionsforanewAndroidproject

7. You’llnextseetheCreateActivityscreen.ChooseBlankActivityastheactivitytype,andclickNexttomovetothelastscreenofthewizard.

8. ThefinalscreenoftheNewAndroidApplicationwizardwillbetheBlankActivitydetailspage.TypeHelloActivityastheActivityName.You’retellingAndroidthatthisactivityistheonetolaunchwhenyourapplicationstartsup.Youmayhaveotheractivitiesinyourapplication,butthisisthefirstonetheusersees.AllowtheLayoutNametoauto-populatewiththevalueactivity_hello.

9. ClicktheFinishbutton,whichtellsADTtogeneratetheprojectskeletonforyou.Fornow,opentheHelloActivity.javafileunderthesrcfolderandmodifytheonCreate()methodasfollows:

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);/**createaTextViewandwriteHelloWorld!*/TextViewtv=newTextView(this);tv.setText("HelloWorld!");/**setthecontentviewtotheTextView*/setContentView(tv);}

Youwillneedtoaddanimportandroid.widget.TextView;statementatthetopofthefilewiththeotherimportstogetridoftheerrorreportedbyEclipse.SavetheHelloActivity.javafile.

Toruntheapplication,youneedtocreateanEclipselaunchconfiguration,andyouneedavirtualdeviceonwhichtorunit.We’llrunquicklythroughthesestepsandcomebacklatertomoredetailsaboutAVDs.CreatetheEclipselaunchconfigurationbyfollowingthesesteps:

1. SelectRun RunConfigurations.

2. IntheRunConfigurationsdialogbox,double-clickAndroidApplicationintheleftpane.ThewizardinsertsanewconfigurationnamedNewConfiguration.

3. RenametheconfigurationRunHelloWorld.

4. ClicktheBrowsebutton,andselecttheHelloAndroidproject.

5. LeaveLaunchActionsettoLaunchDefaultActivity.ThedialogshouldappearasshowninFigure1-8.

Figure1-8.ConfiguringanEclipserunconfigurationtorunthe“HelloWorld!”application

6. ClickApplyandthenRun.You’realmostthere!Eclipseisreadytorunyourapplication,butitneedsadeviceonwhichtorunit.AsshowninFigure1-9,you’rewarnedthatnocompatibletargetswerefoundandaskedifyou’dliketocreateone.ClickYes.

Figure1-9.ErrormessagewarningabouttargetsandaskingforanewAVD

7. You’representedwithawindowthatshowstheexistingAVDs(seeFigure1-10).YouneedtoaddanAVDsuitableforyournewapplication.ClicktheNewbutton.

Figure1-10.TheexistingAVDs

8. FillintheCreateAVDformasshowninFigure1-11.SetNametoKitKat,chooseAndroid4.4-APILevel19(orsomeotherversion)fortheTarget,setSDCardSizeto64(for64MB),andchooseothervaluesasshown.ClickCreateAVD.TheManagermayconfirmthesuccessfulcreationofyourAVD.ClosetheAVDManagerwindowbyclickingXintheupper-rightcorner.

Figure1-11.ConfiguringanAVD

NoteYou’rechoosinganewerversionoftheSDKforyourAVD,butyourapplicationcanalsorunonanolderone.ThisisokaybecauseAVDswithnewerSDKscanrunapplicationsthatrequireolderSDKs.Theopposite,ofcourse,isnottrue:anapplicationthatrequiresfeaturesof

anewerSDKwon’trunonanAVDwithanolderSDK.

9. SelectyournewAVDfromthebottomlist.NotethatyoumayneedtoclicktheRefreshbuttontomakeanynewAVDstoshowupinthelist.ClicktheOKbutton.

10. EclipselaunchestheemulatorwithyourveryfirstAndroidapp(seeFigure1-12)!

Figure1-12.HelloAndroidApprunningintheemulator

NoteItmaytaketheemulatorawhiletoemulatethedevicebootupprocess.Oncethebootupprocesshascompleted,youtypicallyseealockedscreen.ClicktheMenubuttonordragtheunlockimagetounlocktheAVD.Afterunlocking,youshouldseeHelloAndroidApprunningintheemulator,asshowninFigure1-11.Beawarethattheemulatorstartsotherapplicationsinthebackgroundduringthestartupprocess,soyoumayseeawarningorerrormessagefromtimetotime.Ifyoudo,youcangenerallydismissittoallowtheemulatortogotothenextstepinthestartupprocess.Forexample,ifyouruntheemulatorandseeamessagelike“applicationabcisnotresponding,”youcaneitherwaitfortheapplicationtostartorsimplyasktheemulatortoforcefullyclosetheapplication.Generally,youshouldwaitandlettheemulatorstartupcleanly.

NowyouknowhowtocreateanewAndroidapplicationandrunitintheemulator.Next,

we’lllookmorecloselyatAVDs,andalsohowtodeploytoarealdevice.

AVDsAnAVDrepresentsadeviceanditsconfiguration.Forexample,youcouldhaveanAVDrepresentingareallyoldAndroiddevicerunningversion1.5oftheSDKwitha32MBSDcard.TheideaisthatyoucreateAVDsyouaregoingtosupportandthenpointtheemulatortooneofthoseAVDswhendevelopingandtestingyourapplication.Specifying(andchanging)whichAVDtouseisveryeasyandmakestestingwithvariousconfigurationsasnap.Earlier,yousawhowtocreateanAVDusingEclipse.YoucanmakemoreAVDsinEclipsebychoosingWindow AndroidVirtualDeviceManager.YoucanalsocreateAVDsusingthecommandlinewiththeutilitynamedandroidunderthetoolsdirectory(e.g.,c:\android-sdk-windows\tools\).androidallowsyoutocreateanewAVDandmanageexistingAVDs.Forexample,youcanviewexistingAVDs,moveAVDs,andsoonbyinvokingandroidwiththe“avd”option.Youcanseetheoptionsavailableforusingandroidbyrunningandroid-help.Fornow,let’sjustcreateanAVD.

RunningonaRealDeviceThebestwaytotestanAndroidappistorunitonarealdevice.AnycommercialAndroiddeviceshouldworkwhenconnectedtoyourworkstation,butyoumayneedtodoalittleworktosetitup.IfyouhaveaMac,youdon’tneedtodoanythingexceptplugitinusingtheUSBcable.Then,onthedeviceitself,chooseSettings Applications Development(thoughthismayvarybyphoneandversion)andenableUSBdebugging.OnLinux,youprobablyneedtocreateormodifythisfile:/etc/udev/rules.d/51-android.rules.Weputacopyofthisfileonourwebsitewiththeprojectfiles;copyittotheproperdirectory,andmodifytheusernameandgroupvaluesappropriatelyforyourmachine.Then,whenyoupluginanAndroiddevice,itwillberecognized.Next,enableUSBdebuggingonthedevice.

ForWindows,youhavetodealwithUSBdrivers.GooglesuppliessomewiththeAndroidpackages,whichareplacedundertheusb_driversubdirectoryoftheAndroidSDKdirectory.Otherdevicevendorsprovidedriversforyou,solookforthemontheirwebsites.YoucanalsovisittheXDAforums,forum.xda-developers.com,whereadviceonsourcingandconfiguringdriversforavarietyofphonesanddevicesisdiscussed.Whenyouhavethedriverssetup,enableUSBdebuggingonthedevice,andyou’reready.

Nowthatyourdeviceisconnectedtoyourworkstation,whenyoutrytolaunchanapp,eitheritlaunchesdirectlyonthedeviceor(ifyouhaveanemulatorrunningorotherdevicesattached)awindowopensinwhichyouchoosewhichdeviceoremulatortolaunchinto.Ifnot,tryeditingyourRunConfigurationtomanuallyselectthetarget.

ExploringtheStructureofanAndroidApplicationAlthoughthesizeandcomplexityofAndroidapplicationscanvarygreatly,theirstructuresaresimilar.Figure1-13showsthestructureofthe“HelloWorld!”appyoujustbuilt.

Figure1-13.Thestructureofthe“HelloWorld!”application

Androidapplicationshavesomeartifactsthatarerequiredandsomethatareoptional.Table1-1summarizestheelementsofanAndroidapplication.

Table1-1.TheArtifactsofanAndroidApplication

Artifact Description Required?

AndroidManifest.xml

TheAndroidapplicationdescriptorfile.Thisfiledefinestheactivities,contentproviders,services,andintentreceiversoftheapplication.Youcanalsousethisfiletodeclarativelydefinepermissionsrequiredbytheapplication,aswellasinstrumentationandtestingoptions.

Yes

src Afoldercontainingallofthesourcecodeoftheapplication. Yes

assets Anarbitrarycollectionoffoldersandfiles. No

resAfoldercontainingtheresourcesoftheapplication.Thisistheparentfolderofdrawable,animator,layout,menu,values,xml,andraw.

Yes

drawable Afoldercontainingtheimagesorimage-descriptorfilesusedbytheapplication. No

animator AfoldercontainingtheXML-descriptorfilesthatdescribetheanimationsusedbytheapplication. No

layout Afoldercontainingviewsoftheapplication. No

menu AfoldercontainingXML-descriptorfilesformenusintheapplication. No

values Afoldercontainingotherresourcesusedbytheapplication.Examplesofresourcesfoundinthisfolderincludestrings,arrays,styles,andcolors. No

xml AfoldercontainingadditionalXMLfilesusedbytheapplication. No

raw Afoldercontainingadditionaldata—possiblynon-XMLdata—thatisrequiredbytheapplication. No

AsyoucanseefromTable1-1,anAndroidapplicationisprimarilymadeupofthreemandatorypieces:theapplicationdescriptor,acollectionofvariousresources,andtheapplication’ssourcecode.IfyouputasidetheAndroidManifest.xmlfileforamoment,youcanviewanAndroidappinthissimpleway:youhavesomebusinesslogicimplementedincode,andeverythingelseisaresource.

AndroidhasalsoadoptedtheapproachofdefiningviewsviamarkupinXML.Youbenefitfromthisapproachbecauseyoudon’thavetohard-codeyourapplication’sviews;youcanmodifythelookandfeeloftheapplicationbyeditingthemarkup.

Itisalsoworthnotingafewconstraintsregardingresources.First,Androidsupportsonlyasingle-levellistoffileswithinthepredefinedfoldersunderres.Forexample,therearesomesimilaritiesbetweentheassetsfolderandtherawfolderunderres.Bothfolderscancontainrawfiles,butthefilesinrawareconsideredresources,andthefilesinassetsarenot.Sothefilesinrawarelocalized,accessiblethroughresourceIDs,andsoon.Butthecontentsoftheassetsfolderareconsideredgeneral-purposecontenttobeusedwithoutresourceconstraintsandsupport.Notethatbecausethecontentsofthe

assetsfolderarenotconsideredresources,youcanputanarbitraryhierarchyoffoldersandfilesinthisfolder.(Chapter3talksalotmoreaboutresources.)

NoteYoumayhavenoticedthatXMLisusedquiteheavilywithAndroid.YouknowthatXMLcanbeabloateddataformat,sodoesitmakesensetorelyonXMLwhenyouknowyourtargetisadevicewithlimitedresources?ItturnsoutthattheXMLyoucreateduringdevelopmentisactuallycompileddowntobinaryusingtheAndroidAssetPackagingTool(AAPT).Therefore,whenyourapplicationisinstalledonadevice,thefilesonthedevicearestoredasbinary.Whenthefileisneededatruntime,thefileisreadinitsbinaryformandisnottransformedbackintoXML.Thisgivesyouthebenefitsofbothworlds—yougettoworkwithXML,andyoudon’thavetoworryabouttakingupvaluableresourcesonthedevice.

ExaminingtheApplicationLifeCycleThelifecycleofanAndroidapplicationisstrictlymanagedbythesystem,basedontheuser’sneeds,availableresources,andsoon.Ausermaywanttolaunchawebbrowser,forexample,butthesystemultimatelydecideswhethertostarttheapplication.Althoughthesystemistheultimatemanager,itadherestosomedefinedandlogicalguidelinestodeterminewhetheranapplicationcanbeloaded,paused,orstopped.Iftheuseriscurrentlyworkingwithanactivity,thesystemgiveshighprioritytothatapplication.Conversely,ifanactivityisnotvisibleandthesystemdeterminesthatanapplicationmustbeshutdowntofreeupresources,itshutsdownthelower-priorityapplication.

Theconceptofapplicationlifecycleislogical,butafundamentalaspectofAndroidapplicationscomplicatesmatters.Specifically,theAndroidapplicationarchitectureiscomponent-andintegration-oriented.Thisallowsarichuserexperience,seamlessreuse,andeasyapplicationintegrationbutcreatesacomplextaskfortheapplicationlife-cyclemanager.

Let’sconsideratypicalscenario.Auseristalkingtosomeoneonthephoneandneedstoopenane-mailmessagetoansweraquestion.Theusergoestothehomescreen,opensthemailapplication,opensthee-mailmessage,clicksalinkinthee-mail,andanswersthefriend’squestionbyreadingastockquotefromawebpage.Thisscenariorequiresfourapplications:thehomeapplication,atalkapplication,ane-mailapplication,andabrowserapplication.Astheusernavigatesfromoneapplicationtothenext,theexperienceisseamless.Inthebackground,however,thesystemissavingandrestoringapplicationstate.Forinstance,whentheuserclicksthelinkinthee-mailmessage,thesystemsavesmetadataontherunninge-mailmessageactivitybeforestartingthebrowser-applicationactivitytolaunchaURL.Infact,thesystemsavesmetadataonanyactivitybeforestartinganothersothatitcancomebacktotheactivity(whentheuserbacktracks,forexample).Ifmemorybecomesanissue,thesystemhastoshutdownaprocessrunninganactivityandresumeitasnecessary.

Androidissensitivetothelifecycleofanapplicationanditscomponents.Therefore,you

needtounderstandandhandlelife-cycleeventsinordertobuildastableapplication.TheprocessesrunningyourAndroidapplicationanditscomponentsgothroughvariouslife-cycleevents,andAndroidprovidescallbacksthatyoucanimplementtohandlestatechanges.Forstarters,youshouldbecomefamiliarwiththevariouslife-cyclecallbacksforanactivity(seeListing1-1).

Listing1-1.Life-CycleMethodsofanActivity

protectedvoidonCreate(BundlesavedInstanceState);protectedvoidonStart();protectedvoidonRestart();protectedvoidonResume();protectedvoidonPause();protectedvoidonStop();protectedvoidonDestroy();

Listing1-1showsthelistoflife-cyclemethodsthatAndroidcallsduringthelifeofanactivity.It’simportanttounderstandwheneachofthemethodsiscalledbythesysteminordertoensurethatyouimplementastableapplication.Notethatyoudonotneedtoreacttoallofthesemethods.Ifyoudo,however,besuretocallthesuperclassversionsaswell.Figure1-14showsthetransitionsbetweenstates.

Figure1-14.Statetransitionsofanactivity

Thesystemcanstartandstopyouractivitiesbasedonwhatelseishappening.AndroidcallstheonCreate()methodwhentheactivityisfreshlycreated.onCreate()isalwaysfollowedbyacalltoonStart(),butonStart()isnotalwaysprecededbyacalltoonCreate()becauseonStart()canbecalledifyourapplicationwasstopped.WhenonStart()iscalled,youractivityisnotvisibletotheuser,butit’sabouttobe.onResume()iscalledafteronStart(),justwhentheactivityisintheforegroundandaccessibletotheuser.Atthispoint,theusercaninteractwithyouractivity.

Whentheuserdecidestomovetoanotheractivity,thesystemcallsyouractivity’sonPause()method.FromonPause(),youcanexpecteitheronResume()oronStop()tobecalled.onResume()iscalled,forexample,iftheuserbringsyouractivitybacktotheforeground.onStop()iscalledifyouractivitybecomesinvisibleto

theuser.IfyouractivityisbroughtbacktotheforegroundafteracalltoonStop(),thenonRestart()iscalled.Ifyouractivitysitsontheactivitystackbutisnotvisibletotheuser,andthesystemdecidestokillyouractivity,onDestroy()iscalled.

Asadeveloper,youneedn’tdealwitheverypossiblescenario;youmostlyhandleonCreate(),onResume(),andonPause().YouhandleonCreate()tocreatetheuserinterfaceforyouractivity.Inthismethod,youbinddatatoyourwidgetsandwireupanyeventhandlersforyourUIcomponents.InonPause(),youwanttopersistcriticaldatatoyourapplication’sdatastore:it’sthelastsafemethodthatiscalledbeforethesystemkillsyourapplication.onStop()andonDestroy()arenotguaranteedtobecalled,sodon’trelyonthesemethodsforcriticallogic.

Thetakeawayfromthisdiscussion?Thesystemmanagesyourapplication,anditcanstart,stop,orresumeanapplicationcomponentatanytime.Althoughthesystemcontrolsyourcomponents,theydon’trunincompleteisolationwithrespecttoyourapplication.Inotherwords,ifthesystemstartsanactivityinyourapplication,youcancountonanapplicationcontextinyouractivity.

SimpleDebuggingTheAndroidSDKincludesahostoftoolsthatyoucanusefordebuggingpurposes.ThesetoolsareintegratedwiththeEclipseIDE(seeFigure1-15forasmallsample).

Figure1-15.DebuggingtoolsthatyoucanusewhilebuildingAndroidapplications

OneofthetoolsthatyouusethroughoutAndroiddevelopmentisLogCat.Thistooldisplaysthelogmessagesyouemitusingandroid.util.Log,exceptions,System.out.println,andsoon.AlthoughSystem.out.printlnworks,andthemessagesappearintheLogCatwindow,tologmessagesfromyourapplicationyoushouldusetheandroid.util.Logclass.Thisclassdefinesthefamiliarinformational,warning,anderrormethodsthatyoucanfilterintheLogCatwindowtoseejustwhatyouwanttosee.HereisasampleLogcommand:

Log.v("stringTAG","Thisismyverbosemessagetowritetothelog");

Thisexampleshowsthestaticv()methodoftheLogclass,butthereareothersfordifferentlevelsofseverity.It’sbesttousetheappropriatecalllevelforthemessageyouwanttolog,anditgenerallyisn’tagoodideatoleaveaverbosecallinanappthatyou

wanttodeploytoproduction.KeepinmindthatloggingusesmemoryandtakesCPUresources.What’sparticularlyniceaboutLogCatisthatyoucanviewlogmessageswhenyou’rerunningyourapplicationintheemulator,butyoucanalsoviewlogmessageswhenyou’veconnectedarealdevicetoyourworkstationandit’sindebugmode.Infact,logmessagesarestoredsuchthatyoucanevenretrievethemostrecentmessagesfromadevicethatwasdisconnectedwhenthelogmessageswererecorded.WhenyouconnectadevicetoyourworkstationandyouhavetheLogCatviewopen,youseethelastseveralhundredmessages.

LaunchingtheEmulatorEarlieryousawhowtolaunchtheemulatorfromyourprojectinEclipse.Inmostcases,youwanttolaunchtheemulatorfirstandthendeployandtestyourapplicationsinarunningemulator.Tolaunchanemulatoranytime,firstgototheAVDManagerbyrunningtheAndroidprogramwiththeavdoptionfromthetoolsdirectoryoftheAndroidSDKorfromtheWindowmenuinEclipse.OnceintheManager,choosethedesiredAVDfromthelist,andclickStart.

WhenyouclicktheStartbutton,theLaunchOptionsdialogopens(seeFigure1-16).Thisallowsyoutoscalethesizeoftheemulator’swindowtosuityourdisplayandchangethestartupandshutdownoptions.Thescalingresultscansometimesbeunexpectedlylargeorsmall,sopickthevaluethatworksforyoubasedonyourscreensizeandscreendensity.

Figure1-16.TheLaunchOptionsdialog

YoucanalsoworkwithsnapshotsintheLaunchOptionsdialog.Savingtoasnapshotcausesasomewhatlongerdelaywhenyouexittheemulator.Asthenamesuggests,youarewritingoutthecurrentstateoftheemulatortoasnapshotimagefile,whichcanthenbeusedthenexttimeyoulaunchtoavoidgoingthroughanentireAndroidbootupsequence.Launchinggoesmuchfasterifasnapshotispresent,makingthedelayatsavetimewellworthit—youbasicallypickupwhereyouleftoff.

Ifyouwanttostartcompletelyfresh,youcanchooseWipeUserData.YoucanalsodeselectLaunchfromSnapshottokeeptheuserdataandgothroughthebootupsequence.OryoucancreateasnapshotthatyoulikeandenableonlytheLaunchfromSnapshotoption;thisreusesthesnapshotoverandoversoyourstartupisfastandtheshutdownisfasttoo,becauseitdoesn’tcreateanewsnapshotimagefileeverytimeitexits.ThesnapshotimagefileisstoredinthesamedirectoryastherestoftheAVDimagefiles.Ifyoudidn’tenablesnapshotswhenyoucreatedtheAVD,youcanalwaysedittheAVDandenablethemthere.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://developer.samsung.com/:Samsung’sdevelopersite,withmanyAndroid-relateddevelopmenttools.

http://developer.htc.com/:HTCsiteforAndroiddevelopers.

http://developer.android.com/guide/developing/tools/index.htmlDeveloperdocumentationfortheAndroiddebuggingtools.

www.droiddraw.org/:DroidDrawsite.ThisisaUIdesignerforAndroidapplicationsthatusesdrag-and-droptobuildlayouts.

SummaryThischaptercoveredthefollowingtopicstogetyousetupforAndroiddevelopment:

DownloadingandinstallingtheJDK,EclipseorAndroidStudio,andtheAndroidSDK

HowtomodifyyourPATHvariableandlaunchatoolswindow

InstallingandupgradingtheADTfundamentalconceptsofviews,activities,fragments,intents,contentproviders,services,andtheAndroidManifest.xmlfile

AndroidVirtualDevices(AVDs),whichcanbeusedtotestappswhenyoudon’thaveadevice(ortheparticulardeviceyouwanttotestwith)

Buildinga“HelloWorld!”appanddeployingittoanemulator

Thebasicrequirementstoinitializeanyapplication(projectname,Androidtarget,applicationname,packagename,mainactivity,minimumSDKversion)

Wheretherunconfigurationsareandhowtochangethem

Connectingarealdevicetoyourworkstationandrunningyournewappsonit

TheinnerstructureofanAndroidapp,andthelifecycleofanactivity

LogCat,andwheretolookfortheinternalmessagesfromapps

Optionsavailablewhenlaunchinganemulator,suchassnapshotsandadjustingthescreendisplaysize

Chapter2

IntroductiontoAndroidApplicationArchitectureThefirstchaptercoveredtheenvironmentandtoolsnecessarytodevelopAndroidapplications.ThischapterwillbeabroadintroductorytourofAndroid’sapplicationarchitecture.Wewilldothatbydoingthreethings.First,wewillpresentthearchitectureofanAndroidappbybuildingone.WewillthenpresenttheessentialcomponentsofAndroidarchitecture,namely,activities,resources,intents,activitylifecycle,andsavingstate.Wewillconcludethechapterwithalearningroadmaponhowtousetherestofthebooktocreatesimpletosophisticatedmobileapps.

Inthefirstsectionofthischapter,aone-pagecalculatorappwillgiveyouabird’seyeviewofwritingapplicationsusingtheAndroidSDK.CreatingthisappwilldemonstratehowtocreatetheUI,writeJavacodetocontroltheUI,andbuildanddeploytheapp.

InadditiontodemonstratingtheUI,thiscalculatorappwillintroduceyoutoactivities,resources,andintents.TheseconceptsgototheheartofAndroidapplicationarchitecture.WewillcoverthesetopicsindetailinthesecondsectionofthechapterinordertogiveyouastrongfootingforunderstandingtherestoftheAndroidSDK.Wewillalsocovertheactivitylifecycleandabriefoverviewofthepersistenceoptionsforyourapplication.

InthethirdsectionwewillgiveyouaroadmapfortherestofthebookthataddressesbasicandadvancedaspectsofbuildingAndroidapplications.Thisfinalsectionbreaksthechaptersintoasetoflearningtracks.ThissectionisabroadintroductiontotheentiresetofAndroidAPIs.

Furthermore,inthischapteryouwillfindanswerstothefollowing:HowcanIcreateUIwitharichsetofcontrols?HowcanIstorestatepersistently?HowcanIreadstaticfilesthatareinputstotheapp?HowcanIreachoutandreadfromorwritetotheweb?WhatotherAPIsdoesAndroidprovidetomakemyappfunctionalandrich?

Withoutfurtherado,let’sdropyouintothesimplecalculatorapplicationtoopenuptheworldofAndroid.

ExploringaSimpleAndroidApplicationThecalculatorapplicationwewanttodemonstrateforthischapterisshowninFigure2-1.

Figure2-1.ACalculatorApp

DisplayinFigure2-1iscalledanactivityinAndroid.Thisactivityhastwoeditcontrolsatthetoprepresentingtwonumbers.Youcanenternumbersintheseeditboxesandusetheoperatorbuttonsatthebottomofthefiguretoperformarithmeticaloperations.Theresultofanoperationwillbeshowninthetopeditcontrol.ThesetwoeditboxesarelabeledOperand1andOperand2.TocreatethistypeofacalculatorapplicationusingtheAndroidSDK,youneedtoperformthefollowingsteps:

1. CreateaUserInterface(UI)definitioninatext/xmlfile(calledalayoutoralayoutfileinAndroid).

2. WriteprogramminglogicinaJavafile(usuallyinaclassextendingthebaseactivityclass).

3. Createaconfigurationfiledescribingyourapplication(thisfileisalwayscalledAndroidManifest.xml).

4. Createaprojectandadirectorystructuretoplacethefilesfromsteps1,2,and3.

5. Buildadeployablepackageusingtheprojectinstep4(itiscalledan.apkfile).

BygoingthroughthedetailsofthesestepsyouwillgetafeelforhowAndroidapplicationsaremade.Wewillgothroughthesestepsnow.

DefiningUIthroughLayoutFilesAnAndroidapplicationresemblesawebapplicationinlotsofways.Inawebapplication

theUIisyourwebpage.UIofawebpageisdefinedthroughHTML.AnHTMLwebpageisaseriesofcontrolslikeparagraphs,divisions,forms,buttons,etc.UIisconstructedsimilarlyinAndroid.AlayoutfileinAndroidislikeanHTMLpage,albeitthecontrolsaredrawnfromtheAndroidSDKinsteadofHTML.InAndroidthisfileiscalledalayoutfile.Listing2-1showsthelayoutfilethatproducedtheUIofFigure2-1.Listing2-1.AnAndroidLayoutFilethatDefinesUIforanActivity

<?xmlversion="1.0"encoding="utf-8"?><!--**********************************************calculator_layout.xml*correspondingactivity:CalculatorMainActivity.java*prefix:cl_(Usedforprefixinguniqueidentifiers)**Use:*Demonstrateasimplecalculator*Demonstratetextviews,edittext,buttons,businesslogic*********************************************--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="5dp"android:padding="5dp"android:background="@android:color/darker_gray"><!--Operand1--><TextViewandroid:layout_width="match_parent"

android:layout_height="wrap_content"android:text="Operand1,(AndResult)"/><EditTextandroid:layout_width="match_parent"

android:layout_height="wrap_content"android:id="@+id/editText1"android:text="0"android:inputType="numberDecimal"/><!--Operand2--><TextViewandroid:layout_width="match_parent"

android:layout_height="wrap_content"android:text="Operand2"android:layout_marginTop="10dp"/><EditTextandroid:layout_width="match_parent"

android:layout_height="wrap_content"android:text="0"android:id="@+id/editText2"android:inputType="numberDecimal">

</EditText><!--ButtonsforVariousOperators--><TextViewandroid:layout_width="match_parent"

android:layout_height="wrap_content"android:text="Operand1=Operand1OperatorOperand2"android:layout_marginTop="10dp"/><LinearLayoutandroid:orientation="horizontal"android:layout_marginTop="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:text="+"android:id="@+id/plusButton"

android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="-"android:id="@+id/minusButton"

android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="*"android:id="@+id/multiplyButton"

android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="/"android:id="@+id/divideButton"

android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button></LinearLayout></LinearLayout>

Let’sgothroughthiscalculatorXMLlayoutfileofListing2-1linebyline.ThisfilelookscomplicatedcomparedtoFigure2-1.Yes,itisverbose,butyouwillseeshortlyitissimpleinitsarchitecture.

SpecifyingCommentsinLayoutFilesAsagoodpracticethecommentsatthetopofthelayoutXMLfileinListing2-1indicatewhatthisfilenameis,whatUIactivitywillbeusedtodisplaythisfile,whatthepurposeofthisfileis,andbrieflywhatcontrolsareinthislayoutfile.

AddingViewsandViewGroupsinLayoutFiles

EachXMLnodeinalayoutfilerepresentsaUIcontrol.Thesecontrolscanbeeitherviewsorcontainersofotherviews.AcontainerofotherviewsiscalledaViewGroup.Forexample,abuttonisaview.ALinearLayoutinListing2-1isaViewGroupthatplacesallitschildviewseitherverticallydownorhorizontallyacross.So,aLinearLayoutislikeanHTMLdivthatlaysoutitschildreneitheracrossordown.

SpecifyingControlPropertiesinLayoutFilesTheUIcontrolsinthecalculatorlayoutfileareLinearLayout,TextView,EditText,andabutton.EachofthesecontrolsrepresentsaJavaobjectwhenpaintedonthescreen.Beinganobject,eachofthesecontrolshasproperties.IfthecontrolsbelongtothecoreAndroidSDK,theirpropertiesareprefixedwith“android:”asin“android:orientation”fortheLinearLayoutcontrol.Themajority,ifnotall,ofthecontrolsthatyounormallyuseinyourappsarefromthecoreAndroidSDK.Whenyouwriteyourowncontrolstheyarecalledcustomcontrols.Thesecustomcontrolsallowyoutodefinecustomproperties.Seethe“Roadmap”sectionofthischapterformoreoncustomcontrols.

IndicatingViewGroupPropertiesSomeofthecontrolpropertiesarelabeledas“android:layout_,”suchasandroid:layout_width.Theseproperties,althoughmentionedinagivenXMLnode,likeabutton,arereadandusedbyparentnode,likeLinearLayout,toplacethechildren.ParentnodesareviewgroupsliketheLinearLayout.YoucanseethisdifferenceinhowpaddingandmarginsaredefinedforthefirstLinearLayoutnodeinthelayoutfileofListing2-1.ThepropertypaddingbelongstothetopmostLinearLayoutobjectinthisexample,whereasthepropertyformarginsofthatsametopmostLinearLayout,thelayout_marginproperty,belongstotheparentoftheLinearLayout,whichisanimplicitviewgroupprovidedbytheAndroidframework.Soforpaddingyousayandroid:padding,andformarginsyousayandroid:layout_margin.Noticethepresenceorlackof“layout_”prefix.Ifyouwanttoknowwhatpropertiesanobject(orcontrol)supports,youcanuseCtrl-Spaceineclipsetoseeasetofsuggestionsforthepropertiesforthatobject.Dependingonyourdevelopmentenvironmentyoucaneasilyfindanequivalentsetofkeycombinationstodothesame.

ControllingWidthandHeightofaControlTwooften-usedpropertiesforacontrolareitslayoutwidthandlayoutheight.Thelayoutparentofacontrolmanagesthesevalues.Valuesforthesepropertiesaretypicallymatch_parentandwrap_content.IfyousayyourTextViewissettomatch_parentforitswidth,thewidthofthecontrolmatchesupwiththeparentwidth.WhenaTextViewissetforitsheightwrap_content,thenitsheightwillbejustsufficienttocontainallitstextintheverticaldirection.Ofcourse,thesetwopropertiesare

availabletoallchildcontrolsofalayout,notjustthetextcontrol.Thesetwolayoutcontrolproperties,match_parentandwrap_content,alsoapplytotheheightofacontrolaswell.

IntroducingResourcesandBackgroundsAlthoughweareinthemiddleofexplainingthecontrolsinthelayoutfile,thisisagoodplacetointroduceresources.Layoutfilesare,andaremadeupof,resources.Inthecalculatorlayoutfile,wehavesetthebackgroundoftheentireviewbysettingthebackgroundontherootLinearLayoutcontrol.Thisinstructionlookedlikethefollowing:

android:background="@android:color/darker_gray"

EveryvieworcontrolinAndroidsupportsthebackgroundproperty.Backgroundsareusuallyidentifiedasresources.Inthisexample,thebackgroundispointingtoaresource,comingfromtheAndroidpackage,whichisoftypecolorwhosereferencedvalueisdarkergray.

AnumberofinputstoyourapplicationarerepresentedasresourcesinAndroid.Someexampleresourcesareimagefiles,entirelayoutfiles,colors,strings,XMLfiles,menus,andmanyotherthingsaslistedintheAndroidSDK.Forinstance,theentirecalculatorlayoutfilewearetalkingaboutisitselfaresource.

Asyoucanseefromthecalculatorlayoutfile,resourcesareofdifferenttypes.InAndroidtheyarefurtherbroadlyclassifiedas“valuebased”or“filebased.”Examplesofresourcesthatarevaluesarestringsandcolors.Examplesofresourcesthatarefilesareimagesorlayoutfiles.Listing2-2showsanexampleofcreatingvalue-basedresourcesthatarestringsandcolors.

Listing2-2.ExampleofValue-BasedResources

<?xmlversion="1.0"encoding="utf-8"?><!--thisfilewillbein/res/valuessubdirectory--><resources><stringname="hello">HelloWorld,CalculatorMainActivity!</string><stringname="app_name">ADemoCalculator</string><colorname="red">#FF0000</color><colorname="blue">#0000FF</color></resources>

Youcanhaveanynumberofvalue-basedfilesaslongastheyareallunderthe/res/valuessubdirectory.Eachfilewillstartwiththeresourcesrootnode.YoucanuseCtrl-Spacetodiscoverwhatotherpossiblevalue-basedresourcesareavailable.

Turningtofile-basedresources,Listing2-3showsanexampleofplacinganumberoffile-basedresourcesundertheirrespectiveresourcesubdirectories.

Listing2-3.ExampleofFile-BasedResources

/res/layout/page1_layout.xml(Alayoutfileforsaypage1)/res/drawable/page1_background.jpg(Anexampleimagefile)/res/drawable-hdpi/page1_background.jpg(Sameimagefileforadifferentdensity)/res/xml/some_preferences.xml(exampleofaninputfileforyourapp)

Anyoftheseresources,beitfilebasedorvaluebased,canbereferencedinthelayoutfilesusingthe“@”resourcereferencesyntax.Forexample,inthecalculatorlayoutfileinListing2-1thebackgroundcanbesetliterallyandexplicitlyasacolorvaluebetweenthequotes,suchas“#FFFFFF,”orpointtoaresourcereference(indicatedbyastarting@color/red)thatisalreadydefinedasacolorresource(asinListing2-2).Inthissyntaxledby“@,”thetypeofreferencedresourceis“color.”Someofthekeywordsforothertypesofresourcesarestring(forstrings),drawable(forimages),etc.

InListing2-1,thewaytoreadthevalueofthebackgroundpropertyoftheLinearLayout,namely,@android:color/darker_gray,isasfollows:Usethevalueoftheresourceidentifiedasdarker_grayintheAndroidcoreframeworkandwhoseresourcetypeiscolor.Withthisknowledgeofresourcereferencesyntax,takealookatthecalculatorlayoutfilelistingonemoretimeandyouwillbeabletoreaditwhereeachcontrolhaspropertiesandeachpropertyhasavaluethatiseitherdirectlyspecifiedorreferencesaresourcethatiselsewheredefinedinaresourcefile.

Theindirectionofacontrolpropertyvaluedefinedasaresourcereferencehasanadvantage.Aresourcecanbecustomizedforlanguages,devicedensityvariation,andavarietyoffactorswithoutalteringthecompiledJavasourcecode.ForexamplewhenyousupplybackgroundimagesyoucanplaceanumberoftheseimagesindifferentdirectoriesandnamethemusingtheconventionspecifiedbyAndroid.ThenAndroidknowshowtolocatetherightimage,givenitsname,dependingonthedeviceyourappisrunningon.

WorkingwithTextControlsintheLayoutFileInthecalculatorlayoutexamplewehaveusedtwotext-basedcontrols.OneisaTextViewcontrol,whichisusedasalabel;theotherisanEditTextcontrol,whichisusedfortakinginputtext.Wehavealreadyshownyouhowtosetthewidthandheightofanyviewbyusingtheattributesthatstartwith“layout_”.Everytext-basedcontrolalsohasanattributecalledtext.Inourexampleswehavedirectlyspecifiedtheliteraltextasavalueforthisproperty.Therecommendationistouseinsteadaresourcereference.Forexample:

android:text="Literaltext"//whatwedidforclarityorandroid:text="@string/LiteralTextId"//doingitproperly

ThelatterresourceID,LiteralTextId,thencanbedefinedinafileinthe

/res/valuessubdirectorymuchlikeinListing2-2.

EditTextcontrolinthecalculatorlayouthasanattributeinputTypetoprovidethenecessaryconstraintsandvalidationsthatneedtotakeplacewhendataistypedintotheeditablefield.Refertothedocumentationtoseealargenumberofconstraintsthatareavailableforeditablefields.Alternatively,youcanuseeclipseADTtodiscovertheavailableinputtypesontheflyduringcoding.

WorkingwithAutogeneratedIDsforControlsTomanipulatethecontrolsthatareinthecalculatorlayoutofListing2-1,weneedawaytoturnthemintoJavaobjects.ThisisdonebylocatingthesecontrolsusingauniqueIDinthecurrentlyloadedlayoutfileofanactivity.Let’slookatoneexampleinthelayoutfilewhereanEditTextcontrolisgivenanIDofeditText2asfollows:

android:id="@+id/editText2"

ThisformattellsAndroidthatIDofthisEditTextcontrolisaresourceoftypeIDanditsintegervalueshouldbeknowninJavaaseditText2.The+isaconveniencetoallocateanewuniqueintegerforeditText2.Ifyoudon’thavethe+sign,thenAndroidlooksforaninteger-valuedresourcedefinedwithanIDthatiscallededitText2.Withtheconvenienceof+wecanavoidseparatelydefiningaresourcefirstandthenuseit.Insomecases,youmayhaveaneedforawell-knownIDthatissharedbymultiplepiecesofcode,inwhichcaseyouwillremovethe+andtakethemultiplestepsofdefiningtheIDfirstandthenusingitsnameinmultipleplaces.Youwillseeintheprogramminglogicsection(soontofollow)howthesecontrolIDsareusedtolocatethecontrolsandmanipulatethem.

ImplementingProgrammingLogicToseethecalculatorlayoutonthescreenofyourdevice,youneedaJavaclassderivedfromtheAndroidSDKsclassactivity.Suchanactivityrepresentsawindowinyourmobileapplication.SoyouneedtocraftacalculatoractivitybyextendingtheAndroidbaseactivityclassasshowninListing2-4.

Listing2-4.ProgrammingLogic:ImplementinganActivityClass

/***Activityname:CalculatorMainActivity*Layoutfile:calculator_layout.xml*Layoutshortcutprefixforids:cl_*Menufile:none*PurposeandLogic********************1.Demonstratebusinesslogicforasimplecalculator*2.Loadthecalculator_layout.xmlaslayout

*3.Setupbuttoncallbacks*4.Respondtobuttonclicks*5.Readvaluesfromedittextcontrols*6.Performoperationandupdateresulteditcontrol*/publicclassCalculatorMainActivityextendsActivity

implementsOnClickListener{privateEditTextnumber1EditText;privateEditTextnumber2EditText;

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);setContentView(R.layout.calculator_layout);gatherControls();setupButtons();}privatevoidgatherControls(){

number1EditText=(EditText)this.findViewById(R.id.editText1);number2EditText=(EditText)this.findViewById(R.id.editText2);number2EditText.requestFocus();}privatevoidsetupButtons(){

Buttonb=(Button)this.findViewById(R.id.plusButton);b.setOnClickListener(this);

b=(Button)this.findViewById(R.id.minusButton);b.setOnClickListener(this);

b=(Button)this.findViewById(R.id.multiplyButton);b.setOnClickListener(this);

b=(Button)this.findViewById(R.id.divideButton);b.setOnClickListener(this);}@OverridepublicvoidonClick(Viewv){

StringsNum1=number1EditText.getText().toString();StringsNum2=number2EditText.getText().toString();doublenum1=getDouble(sNum1);doublenum2=getDouble(sNum2);Buttonb=(Button)v;

doublevalue=0;

if(b.getId()==R.id.plusButton){value=plus(num1,num2);}elseif(b.getId()==R.id.minusButton){value=minus(num1,num2);}elseif(b.getId()==R.id.multiplyButton){value=multiply(num1,num2);}elseif(b.getId()==R.id.divideButton){value=divide(num1,num2);}number1EditText.setText(Double.toString(value));}

privatedoubleplus(doublen1,doublen2){

returnn1+n2;}privatedoubleminus(doublen1,doublen2){

returnn1-n2;}privatedoublemultiply(doublen1,doublen2){

returnn1*n2;}privatedoubledivide(doublen1,doublen2){

if(n2==0){return0;}returnn1/n2;}privatedoublegetDouble(Strings){

if(validString(s)){returnDouble.parseDouble(s);}return0;}privatebooleaninvalidString(Strings){return!validString(s);}privatebooleanvalidString(Strings){if(s==null){returnfalse;}if(s.trim().equalsIgnoreCase("")){returnfalse;}returntrue;

}}

Inthislisting,thecalculatoractivityiscalledCalculatorMainActivity.Onceyouhavethisactivity,youcanloadthecalculatorlayoutintoitinordertoseethecalculatorscreenofFigure2-1.

Let’slearnabitaboutanactivityinAndroid.Aprogrammerdoesnotneedtoinstantiateanactivitydirectly.AnactivitycanbeinstantiatedbytheAndroidframeworkbasedonuser’sactions.Inthatsense,anactivityisa“managedcomponent”managedbyAndroid.

AnactivitycangetpartiallyhiddenorcompletelyhiddenwhenanotherUIwithhigherprioritysitsontopofit(forexample,duetoaphonecall).Or,anactivitythatisinthebackgroundcanbetemporarilyremovedduetomemoryconstraint.Inthesecircumstances,theactivitycanbeautomaticallybroughtbackwhenauserrevisitstheapplication.

LoadingtheLayoutFileintoanActivityAstheactivityiseventdriven,anactivityreliesoncallbacks.ThefirstcallbackofimportanceistheonCreate()callback.InthecalculatoractivitygiveninListing2-4,youcaneasilylocatethismethod.Thisiswherewewillloadthecalculatorlayoutintothecalculatoractivity.ThisisdonethroughthemethodsetContentView().Theinputtothismethodisanidentifierforthecalculatorlayoutfile.

AnicefeatureofAndroidiswhatitdoeswiththevariousresourcesincludingthelayoutfiles.ItautogeneratesajavaclasscalledR.javawhereitdefinesintegerIDsforalltheresourcesbetheyvaluebasedorfilebased.IntheactivitygiveninListing2-4,thevariableR.layout.calculator_layoutpointstothecalculatorlayoutfile(whichitselfisinListing2-1).

WhenyouaredippingyourtoesintotheAndroidframework,theothermysteriousthinginonCreate()isthesavedInstanceBundle.AstheAndroidframeworkmaystopandrestart(evenre-create)activities,itneedsawaytopassthelaststateoftheactivitytotheonCreate()method.ThatiswhatthesavedInstanceBundleis.Itisacollectionofkeyvaluepairsholdingthepreviousstateoftheactivity.Youwilllearnaboutthisaspectofstatemanagementinmoredetaillaterinthechapter,andalsoinChapter9,wherewecoverwhathappenswhenadeviceisrotated.Fortheimplementationofthecalculatorexamplewesimplycallthesuperclass’smethodtopassonthatstatebundle.

GatheringControlsThenexttwomethods,gatherControls()andsetupButtons(),setuptheinteractionmodelforthecalculator.InthegatherControls()methodyouobtainjavareferencesfortheeditcontrolsthatyouneedtomanipulate(readorwriteto)andsavethemlocallyinthecalculatoractivityclass.YoudothisbyusingthefindViewById()methodonthebaseactivityclass.ThefindViewById()methodtakesasinputtheID

ofthecontrolthatisinthelayoutfile.HerealsoAndroidautogeneratestheseIDsandplacesthemintotheR.javaclass.Inyoureclipseprojectyoucanseethisfileinthe/gensubdirectory.Listing2-5showsthegeneratedR.javafileforthiscalculatorproject.(Ifyouweretotrythisprojectyourself,theseIDsmaydiffer.Sousethislistingprimarilytounderstandconcepts.)

Listing2-5.AutogeneratedResourceIDs:R.java

publicfinalclassR{publicstaticfinalclassattr{}publicstaticfinalclassdrawable{publicstaticfinalintbackground=0x7f020000;publicstaticfinalinticon=0x7f020001;}publicstaticfinalclassid{publicstaticfinalintdivideButton=0x7f050005;publicstaticfinalinteditText1=0x7f050000;publicstaticfinalinteditText2=0x7f050001;publicstaticfinalintminusButton=0x7f050003;publicstaticfinalintmultiplyButton=0x7f050004;publicstaticfinalintplusButton=0x7f050002;}publicstaticfinalclasslayout{publicstaticfinalintcalculator_layout=0x7f030000;}publicstaticfinalclassstring{publicstaticfinalintapp_name=0x7f040001;publicstaticfinalinthello=0x7f040000;}}

NoticehowR.javausesadifferentclassprefixforeachresourcetype.ThisallowstheprogrammerineclipsetoquicklyseparatetheIDsbywhattheirtypeis.So,forexample,allIDsforlayoutfilesareprefixedwithR.layout,andallimageIDsareprefixedwithR.drawable,andallstringswithR.string,etc.However,thereisacautionwhileworkingtheseIDs.EvenifyouhavetenlayoutfilestheIDsforallthecontrolsaregeneratedintoasinglenamespacesuchasR.id.*(where“id”isanexampleofaresourcetype).Soyoumaywanttogetintothehabitofnamingcontrolsinthelayoutfileswithsomeprefixindicatingwhichlayoutfilestheybelongto.

SettingUpButtonsSomeofthecontrolsinthecalculatorlayoutofListing2-1arethecalculatorbuttons.Theyarethebuttonsrepresentingoperators:+,-,x,and/.Weneedcodetobeinvokedwhenthesebuttonsarepressed.Thewaytodothatisbyregisteringacallbackobjectonthebuttoncontrols.Thesecallbackobjectsmustimplementthe

View.OnClickListenerinterface.ThecalculatoractivityinadditiontoextendingtheactivityclassalsoimplementstheView.OnClickListenerinterface,allowingustoregisterouractivityastheonethatneedstobecalledbackwheneachbuttonispressed.Asyoucanseeinthecodeoftheactivity(Listing2-4),thisisdonebycallingthesetOnClickListeneroneachbutton.

RespondingtoButtonClicks:TyingItAllTogetherWhenanyoftheoperatorbuttonsisclicked,theonClick()methodinthecalculatoractivitygiveninListing2-4getscalled.InthismethodwewillinvestigatetheIDoftheviewthatcalledback.Thiscallingviewshouldbeoneofthebuttons.InthismethodwewillreadthevaluesfromboththeEditTextcontrols(theoperandvalues)andtheninvokeamethodthatisspecifictoeachoperator.TheoperatormethodwillcalculatetheresultandupdatetheEditText,whichislabeledResult.

UpdatingtheAndroidManifest.XMLSofarwehavetheUI(intermsofthelayoutfile)andwehavethebusinesslogicintermsofthecalculatoractivity.EveryAndroidappmusthaveitsconfigurationfile.ThisfileiscalledtheAndroidManifest.xml.Thisisavailableintherootdirectoryoftheproject.Listing2-6showstheAndroidManifest.xmlforthisproject.

Listing2-6.ApplicationConfigurationFile:AndroidManifest.xml

<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.calculator"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="14"/><applicationandroid:icon="@drawable/icon"

android:label="@string/app_name">

<activityandroid:name=".CalculatorMainActivity"android:theme="@android:style/Theme.Light"android:label="@string/app_name"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity>

</application></manifest>

Thepackageattributeofthismanifestfilefollowsanamingstructuresimilartothejavanamespaces.Inthecalculatorapp,thepackageissettocom.androidbook.calculator.Thisislikegivinganameandauniqueidentifiertoyourapp.OnceyousignthisappandinstallitonanapppublisherliketheGooglePlayStore,onlyyouwillbeabletoupdateitorreleasesubsequentversionsofit.Theuses-sdkdirectiveindicatestheAPIforwhichthisappisbackwardcompatible.TheapplicationnodehasanumberofpropertiesincludingitslabelandaniconthatwillshowupintheAndroiddeviceappsmenu.Insideanapplicationnodeweneedtodefinealloftheactivitiesthatmakeupthisapplication.Eachactivityisidentifiedbyitsrespectivejavaclassname.Iftheactivityclassnameisnotfullyqualified,thenthejavapackageisassumedtobethesameastheapplicationpackageidentified.Themefortheactivityindicatesasetofpropertiesthattheviewsbelongingtothatactivitywillinherit.ItislikesettingaCSSstyleontheHTMLUI.Androidcomeswithafewdefaultstyles.Choosingalightthemeisgoodforcontrastwhiletakingscreenshots(asshowninFigure2-1).Chapter7isdedicatedtousingstylesandthemesinyourapps.

IntheAndroidapplicationmanifestfileanactivitycanspecifyaseriesofintentfilters.IntentisaprogrammingconceptthatisuniquetoAndroid.Androidreliesheavilyontheseintents.Androidusesintentobjectstoinvokeapplicationcomponentsincludingactivities.Anintentobjectcancontainanexplicitactivityclassnamesothatwhenyouinvokethatintentyouendupinvokingtheactivity.Orinsteadofhavinganexplicitclassname,anintentcanindicateagenericactionlikeVIEWtoviewawebpage.WhenyouinvokesuchanintentwithagenericactionAndroidwillpresentallpossibleactivitiesthatcansatisfythataction.ActivitiesregisterwithAndroidthroughthemanifestfilethattheycanrespondtosomeactionsthroughanintentfilter.Listing2-7showshowyoucaninvokeanactivitythroughanintentobject.

Listing2-7.UsinganIntentObjecttoInvokeanActivity

//currentActivityreferstotheactivityinwhichthiscoderunsIntenti=newIntent(currentActivity,SomeTargetActivity.class);currentActivity.startActivity(i);//startthetargetactivity

AlthoughweusedcurrentActivityasthevalueofthefirstargumenttocreateanintent,allitneedsisabaseclassreferencecalledContext.Acontextreferencerepresentstheapplicationcontextinwhichacomponentlikeanactivityruns.Comingbacktotheintentobject,ithasanumberofflagsandextradataelementsthatyoucanusetocontrolthebehaviorofthetargetactivitythattheintentisinvoking.Listing2-8showsanexample.

Listing2-8.UsingExtrasonanIntentObject

//currentActivityreferstotheactivityinwhichthiscoderunsIntentintent=new

Intent(currentActivity,SomeTargetActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);intent.putExtra("some-key","some-value");currentActivity.startActivity(intent);

Inthisexample,wewantthetargetactivitytobebroughttothetopofthewindoworactivitystackandcloseanyotheractivitiesthatwereontopofitbefore.Asoneinvokesactivitiesfromotheractivitiestheysitontopofeachother.Thisstackallowsthebackbuttontonavigatebacktothepreviousactivityinthestack.Whenyougoback,thecurrenttopactivityisfinishedandthepreviousactivityisshownintheforeground.ThecodeinListing2-8islikegoingbacktothelastpositionofthetargetactivity,makingthatthetopinstance,andremoving/finishingallrecentactivitiesabovethat.Extrasonintentareasetofkeyvaluepairsthatyoucanpasstothetargetactivityfromthesourceactivity.Activitiesareprettyisolatedfromeachother.Theydon’tsharetheirlocalvariablesbetweeneachother.Instead,theyshouldpasstheirdatathroughobjectsthatcanbeserializedanddeserialized.AndroidusesaninterfacesimilartoSerializablecalledParcelablethatallowsgreaterflexibilityandefficiency.

Ultimately,everyactivityisalmostalwaysstartedbyanintentobject.YoucangettheintentobjectanywhereinyourtargetactivitybycallinggetIntent().Onceyougettheintentobject,youcangetitsextrasandseeifthereisanypertinentdatathatyouneed.

Completestudyofintentsandtheirvariationsisalargetopic.Wewillreturntotalkmoreaboutintentslaterinthischapterafterconcludingourdiscussiononthecalculatorapp.WehavealsoincludedaURLforthefreededicatedchapteronintentsfromourpreviouseditionsattheendofthischapter.

PlacingtheFilesintheAndroidProjectLet’sreturntoourmainlineofthought,thecalculatorapp.Bynowyouhavethethreefilesyouneedtocreatethecalculatorapplication.UsewhatyouhavelearnedinthefirstchaptertocreateanemptyAndroidprojectandadjustthatprojecttoplacethesethreefiles.ThesefilesaregiveninListing2-9alongwiththeirparentdirectories.

Listing2-9.PlacementofFilesfortheCalculatorApp

/res/layout/calculator_layout.xml/src/com/androidbook/calculator/CalculatorActivity.java/AndroidManifest.xml

Figure2-2showsthestructureofyourAndroidprojectineclipse.YoucanseetherelativelocationsofthefilesofListing2-9.

Figure2-2.Acalculatorappdirectorystructure

ThedirectorystructureinFigure2-1alsoshowsyouwhereotherresourceslikeimagesandstringsareplaced.Youcanalsoseethedirectorystructureforthedevice-dependentimagefilesinFigure2-2.ThisishowAndroidsolvesthelocalizationmultilingualsupportaswellbyusingdifferentresourcesubdirectorysuffixes.YouwilllearnabouttheothersubdirectoriesofanAndroidprojectasyougothroughthisbook.

TestingtheCalculatorApponaRealDeviceAllthatisleftnowistobuildtheAPKfile,signit,andbereadytodeploy.ThesimplestwaytotestyourprojectistohaveeclipsedeploytheAPKtotheemulatorandtestit.The

simplestwaytotestthisfile(onceitissigned)onadeviceistoe-mailittoyourselfandopenthee-mailonyourdevice.ThereisasecuritysettingonthedevicetoallowAPKsfromunverifiedsources.Aslongasthisisallowed,youwillbeabletoinstalltheAPKfileandrunitonyourdevice.OryoucanalsoconnectthedevicetotheUSBportandhaveeclipsedeploytheAPKdirectlytothedevice.Youcanevendebugitonthedevicethrougheclipse.YoucanalsocopytheAPKfilefromyourPCorMactothedeviceSDcardandinstallitfromthere.Thisconcludesoursectiononthecalculatorapp,whichillustratedthenatureofAndroidapps.Wewillmovetothesecondsectionofthechapternowwherewewilltalkaboutactivitiesinalotmoredepthandalsorevisitresources,intents,andsavingstate.Let’sstartwithactivities.

AndroidActivityLifeCycleAnAndroidactivityisaself-standingcomponentofanAndroidapplicationthatcanbestarted,stopped,paused,restarted,orreclaimeddependingonvariouseventsincludinguser-initiatedandsystem-initiatedones.Soitisreallyimportanttoreviewthearchitectureofthelifecycleofanactivitybylookingatallofitscallbacks.Figure2-3showsthelifecycleofanactivitybydocumentingtheorderofitscallbacksandthecircumstancesunderwhichthosecallbacksareexecuted.Let’sconsiderthesecallbackmethodsonebyone.

Figure2-3.AnnotatedAndroidactivitylifecycle

voidonCreate(BundlesavedInstanceState)Theactivity’slifecyclestartswiththismethod.Inthismethodyoushouldloadyourviewhierarchiesbyloadingthelayoutsintothecontentviewoftheactivity.Youalsoinitializeanyactivitylevelvariablesthatmaybeusedduringthelifetimeoftheactivity.Likemanyofthecallbacksyoualsocalltheparent’sonCreate()methodfirst.

WhenonCreateiscalled,anactivitymaybeinoneofthreestates.Theactivitymayhavebeenabrand-newactivitystartingoutitslifeforthefirsttime.Oritmaybeanactivitythatisautomaticallyrestartedbecauseofaconfigurationchangesuchadevicerotatingfromoneorientationtoanother.Oritisanactivitythatisrestartedfollowingapreviousprocessshutdownduetolow-memoryconditionsandbeinginthebackground.IntheonCreatecallback,youshouldtakethesescenariosintoaccountifwhatyouneedtodoineachscenarioisdifferent.

NowwecanunderstandtheargumenttothismethodinvolvingthesavedInstanceBundle.Youcanusethisbundletolookintothepreviousstateoftheactivity.Thisbundlemayhavebeenoriginallyusedtosavethestateoftheactivityduringaconfigurationchangeorwhentheactivityanditsprocessshutdownduetolow-memoryconditions.Thestatethatissavedintothisbundleargumentisusuallycalledtheinstancestateoftheactivity.Theinstancestateissomewhattemporaryinnature;specifically,itistiedtothisinstanceoftheapplicationduringthisinvocation.Thistypeofstateisnotexpectedtobewrittentopermanentstoragelikefiles.Theuserwillnotbetoodisconcertedifthisstateistoreverttoaninitialstatewhentheapplicationisrevived.InthecallbackwewillexplainsooncalledonPause()youcansavethestatethatmustbepersistedtolong-termstorage.Ifthathappens,youcanusetheonCreate()methodtoloadthatstateaswellaspartofthestart-up.

Thereisanotherconsiderationthatthismethodcantakeintoaccount.Whenanactivityisrestartedorre-createdbecauseofanorientationchange,theoldactivityisdestroyedandanewactivityiscreatedinitsplace.Thismeansthenewactivityhasanewreferenceinmemory.Theoldactivityreferenceisnolongervalid.Itwouldbewrongtohaveanexternalthreadoraglobalobjectthatisholdingontotheoldactivity.Sothereneedstobeamechanismwhentheactivityisre-createdtotelltheexternalobjectthatthereisanewactivityreference.Todothat,there-createdactivityneedstoknowthereferenceofthatexternalobject.Thisexternalobjectreferenceiscalled“non-configurationinstancereference.”ThereisacallbackmethodcalledonRetainNonConfigurationInstance()thatcanreturnareferencetothisexternalobject;weshallcoverthisshortly.AndroidSDKthenkeepsthisreferenceandmakesitavailabletothere-createdactivitythroughamethodcalledgetLastNonConfigurationInstance().NotethatinChapter8,wewillshowyouhowtodothisbetterthroughwhatarecalledheadlessretainedfragments.WewillreturntothistopicalsoinChapter15onAsyncTask.

ThereisanothernuancetotheonCreatemethod.Youmaywanttoensurethatinthe

layoutsyouhaverightviewsandfragments(whichyouwilllearninChapter8)tomatchwhenthestatewassaved.BecauseasubsequentonRestoreInstanceState()(whichiscalledafteronStart())assumesthatalltheviewandfragmenthierarchiesarepresenttorestoretheirrespectivestates,themerepresenceofthepreviousstatewillnotre-createtheviews.Soitisuptothismethodtoloadtherightlayoutstobeshown.Thisisusuallynotanissueifyoudon’tdeleteoraddviewsduringtheinteractionwiththeactivity.

voidonStart()Afterbeingcreated,thismethodpushestheactivityintoavisiblestate.Inotherwordsthismethodstartsthe“visiblelifecycle”oftheactivity.ThismethodiscalledrightafteronCreate().ThismethodassumesthattheviewhierarchiesareloadedandavailablefromtheonCreate().Younormallydon’tneedtodooverridethismethodandifyoudo,makesureyoucalltheparent’sonStart()first.InFigure2-2notethatthismethodcanalsobecalledfromanothercallbackcalledonRestart.

YoumustbeawarethattheonRestoreInstanceStatemethodiscalledafterthismethod.Soyoushouldn’tmakeassumptionsaboutthestateoftheviewsinthismethod.Sotrynottomanipulatethestateoftheviewsinthismethod.DothatrefinementinthesubsequentonRestoreInstanceStateortheonResumemethod.BecausethisisacounterpartoftheonStop(),dothereverseshouldyouhavestoppedsomethinginonStop()orinonPause().Ifyouseesomethingisbeingdoneinthismethod,lookatitwithcautionandmakesureitiswhatyouwant.Alsoknowthatthestart-and-stopcyclecanhappenmultipletimesduringtheoverallcurrentcycleoftheactivity.

Thismethodcanalsobecalledwhentheactivityisshownafterbeinghiddenfirst,becauseanotheractivityhascometothetopofthevisibilitystack.InthosecasesthismethodiscalledaftertheonRestart(),whichitselfistriggeredaftertheonStop().Sotherearetwopathsintothismethod:eitheronCreate()oronRestart().Inbothcasestheviewhierarchiesareexpectedtobeestablishedandavailablepriortothiscallback.

voidonRestoreInstanceState(BundlesavedInstanceState)Ifanactivityistobelegitimatelyclosedbyauser,thenthestatethatuseriswillingtodiscardistheinstancestate.Forexample,whentheuserchoosesabackbutton,thenhe/sheisinformingAndroidthathe/shenolongerisinterestedinthisactivityandthattheactivitycanbeclosed,discardingallitsstatethatisnotyetsaved.Sothisstate,whichistransitoryandonlyvalidforthelifeoftheactivitywhileitisinmemory,istheinstancestate.

Ifthesystemchosestoclosetheactivity,becausethereisachangeinorientation,thentheuserwillexpectthattransitory(instance)staterightbackwhentheactivityisrestarted.Tofacilitatethis,AndroidcallsthisonRestoreInstanceStatemethodwithabundlethatcontainsthesavedinstancestate.(SeetheonSavedInstanceStatemethod

explanation.)

Incontrasttoinstancestate,thepersistentstateofanactivityissomethingtheuserexpectstoseeevenaftertheactivityfinishesandisnolongerinplay.Thispersistencestatemayhavebeencreatedduringtheactivityormayevenexistbeforetheactivityiscreated.Thistypeofstate,especiallywhenitiscreatedwiththehelpoftheactivity,mustbeexplicitlysavedtoanexternalpersistentstorelikeafile.Iftheactivitydoesn’tuseanexplicit“save”buttonforsuchneeds,thenthe“onPause”methodneedstobeusedtosavesuchimplicitpersistentstates.ThisisbecausenomethodafteronPauseisguaranteedtobecalledincaseoflow-memoryconditions.Youshouldn’trelyontheinstancestateiftheinformationistooimportanttolose.

voidonResume()ThecallbackmethodonResumeistheprecursortohavingtheactivityfullyvisible.Thisisalsothestartoftheforegroundcyclefortheactivity.InthisforegroundcycletheactivitycanmovebetweenonResume()andonPause()multipletimesasotheractivities,notifications,ordialogswithmoreurgencycomeontopandgo.

Bythetimethismethodiscalled,wecanexpecttheviewsandtheirstatefullyrestored.Youcantakethisopportunitytotweakfinalstatechanges.Asthismethoddoesn’thaveabundle,youneedtorelyontheinformationfromonCreateoronRestoreInstanceStatemethodstofine-tunestateiffurtherneeded.

IfyouhadstoppedanycountersoranimationsduringonPauseyoucanrestartthemhere.Youcanalsokeeptrackofthecaseiftheviewsarereallydestroyedornotbyfollowingthepreviouscallbackmethods(whetheronResumeisaresultofonCreate,onRestart,oronPause)anddotheminimumpossibletoadjusttheviewstate.Typicallyyouwillnotdostatemanagementherebutonlythosetasksthatneedtobeturnedonoroffbasedonvisibility.

voidonPause()Thiscallbackindicatesthattheactivityisabouttogointobackground.Youshouldstopanycountersoranimationsthatwererunningwhentheactivitywasfullyvisible.TheactivitymaygotoonResumeorproceedtoonStop.GoingtoonResumewillbringtheactivitytotheforeground.GoingtoonStopwilltaketheactivityintoabackgroundstate.

AspertheSDK,itisalsothelastmethodthatisguaranteedtobecalledbeforetheactivityandtheprocessiscompletelyreclaimed.Soitisthelastopportunitythatadeveloperhastosaveanynon-instanceandpersistentdatatoafile.

AndroidSDKalsowaitsforthismethodtoreturnbeforemakingtheforegroundactivityfullyactive.Soyouwanttobebriefinthismethod.Alsonoticethatthismethodhasnobundlethatispassed.Thisisanindicationthatthismethodisforstoringpersistentdataandalsoinanexternalstoragemediumsuchasafileoranetwork.

Youcanalsousethismethodtostopanycounters,animations,orstatusdisplaysofabackgroundtask.YoucanresumetheminonResume.

voidonStop()ThecallbackmethodonStop()movestheactivityfrompartiallyvisibletothebackgroundstatewhilekeepingalloftheviewhierarchiesintact.ThisisthecounterpartofonStart.TheactivitycanbetakenbacktothevisiblecyclebycallingonStart.ThisstatetransitionofgoingfromonStoptoonStartduringthesameactivitylifecyclegoesthroughtheonRestart()method.

Afterthiscall,theactivityisnolongervisible.ButkeepinmindthatthismaynotbecalledafteronPauseunderlow-memoryconditions.Becauseofthisuncertaintydonotusethismethodtostartorstopservicesthatareoutsideofthisprocess.DothatinonPauseinsteadandresumetheminonResume.However,youcanusethismethodtocontrolservicesorworkthatisinsideyourprocess.Thisisbecause,aslongastheprocessisactive,thismethodisgoingtogetcalled.Ifthewholeprocessistakendownthenthosedependenttasksorglobalvariableswillgoawayanyway.

voidonSaveInstanceState(BundlesaveStateBundle)ThecontrolgoestoonDestroy()comingoutofonStopiftheprocessisstillinmemory.However,ifAndroidrealizesthatactivityisbeingclosedwithouttheuser’sexpectationthenitwouldcalltheonSaveInstanceState()beforecallingonDestroy().Orientationchangeisaveryconcreteexampleofthis.TheSDKwarnsthatthetimingofonSaveInstanceState()isnotpredictablewhetherbeforeorafteronStop().

Thedefaultimplementationofthismethodalreadysavesthestateofviews.HoweverifthereissomeexplicitstatethatisnotknowntotheviewsyouneedtosaveitinthebundleobjectandretrieveitbackintheonRestoreInstanceStatemethod.Youdoneedtocalltheparent’sonSaveInstanceState()methodfirstsothatviewshaveanopportunitytosavetheirstatethemselves.Therearesomerestrictionsandrulesfortheviewstobeabletosavetheirstate.ThechaptersonUIcontrols(Chapters3,4,and5)andconfigurationchange(Chapter9)gointomoredetailonthissubject.

voidonRestart()Thismethodiscalledwhentheactivitytransitionsfrombackgroundstatetopartiallyvisiblestate,i.e.,goingfromonStoptoonStart.YoucanusethisknowledgeinonStartifyouwanttooptimizecodetherebasedonwhetheritisafreshstartorarestart.Whenitisarestarttheviewandtheirstatearefairlyintact.

YoucandothingsinthismethodthatwouldhavebeendoneinonStart,butoptimizedwhentheactivityisnotvisible,buttooexpensivetobedonemultipletimesinonResume.

ObjectonRetainNonConfigurationInstance()Thiscallbackmethodisinplacetodealwithactivityre-creationduetoconfigurationchanges.Thismethodreturnsanobjectreferenceinyourprocessmemorythatneedstoberetiedtotheactivityonceitisre-created.WeexplainedthisinmoredetailpreviouslywhenwedescribedtheonCreatemethod.

Whentheactivityisre-created,theobjectthatisreturnedfromthismethodismadeavailablethroughthemethodgetLastNonConfigurationInstance().NowinonCreate()thenewactivitycanusethepreviouslyestablishedresourcesandobjectreferences.Importantly,ifthosepreviousresourcesareholdingtotheoldactivityreference,thentheresourcescanbetoldtousethenewone.

ThisdilemmaexistsbecauseduringanorientationchangeAndroiddoesn’tkilltheprocess,butjustdiscardstheoldactivity,re-createstheactivityintheneworientation,andexpectstheprogrammertosupplynewlayouts,etc.,tosuitthenewconfiguration.Sotheworkingobjectsarestillthereholdingontoanoldactivity.Thisisthemethodinassociationwithits“get”counterparttoovercomethisobstacle.

WhenyoureadChapter8,youwilllearnthatthismethodisdeprecatedandyouwilluseinitsplacewhatarecalledheadlessretainedfragments.Theseheadlessretainedfragmentshavetheadditionalbenefitofbeingabletotracktheactivitylifecycleandnotjustthereferencetotheactivity.

voidonDestroy()onDestroy()isthecounterpartofonCreate().TheactivityisgoingtofinishafteronDestroy.Anactivitycanfinishfortwoprimaryreasons.

Oneisanexplicitclose.Thiscanhappenwhentheuserhasexplicitlycausedtheactivitytofinisheitherbyclickingabuttonthatisprovidedtoindicatethattheuserisdoneorbyusingabackbuttonleavingtheactivitytogotothepreviousactivity.Undersuchcircumstancestheactivitywillnotbebroughtbackbythesystemunlesstheuserchoosestheactivityagain.InthisscenariotheactivitylifecycleendswiththeonDestroymethod.

Thesecondreasonanactivitycancloseisinvoluntary.Whentheorientationofadevicechanges,theAndroidSDKwillforcefullyclosetheactivityandcalltheonDestroymethodfollowedbyre-creatingtheactivityandcallingtheonCreateagain.

Whenanactivityisinthebackgroundandifthesystemneedsmemory,AndroidmayshutdowntheprocessandmaynothaveanopportunitytocalltheonDestroymethod.Duetothisuncertainty,muchlikeonStop,don’tusethismethodtocontroltasksorservicesthatareoutsidetheprocessinwhichtheactivityhadbeenrunning.However,iftheprocessisstillinmemorytheonDestroywillbecalledaspartofthelifecycleandyoucanplacecleanupcodeinonDestroyaslongasthatcodebelongstothisprocess.

GeneralNotesonActivityCallbacks

UseFigure2-3toguideyoutoseetheorderofthesecallbacksandhowbesttousethem.Ifyouweretooverrideacallbackyouneedtocallbacktheparentmethod.TheSDKdocumentationexplicitlyindicateswhichderivedmethodsarerequiredtocallbacktheirparentequivalents.AlsorefertotheSDKdocumentationtolearnduringwhichcallbacksthesystemwillnotkilltheprocessduetolow-memoryconditions.Alsonoticethatonlyahandfulofcallbackscarryinstancestatebundle.

MoreonResourcesWewanttotellyoulittlemoreabouthowresourcesareusedinAndroidapplications.Inthecalculatorlayoutfile,youhaveseensomeoftheresourcesusedlikestrings,images,IDs,etc.

Otherresourcesthatarenotsoobviousincludedimensions,drawables,stringarrays,languagetermsforplurals,xmlfiles,andalltypesofinputfiles.InAndroid,somethingistreatedasaresourcea)ifitisaninputtoyourprogramandispartoftheapkfileandb)ifthevalueorcontentoftheinputcanhavedifferentvaluesbasedonlanguage,locale,ororientationofthedevice,generallycalledaconfigurationchange.

DirectoryStructureofResourcesAllresourcesinAndroidareplacedunderthe/ressubdirectoryoftherootofyourapplicationpackage.Listing2-10showsanexampleofwhata/resmaylooklike:

Listing2-10.AndroidResourceandAssetsDirectoryStructure

/res/values/strings.xml/colors.xml/dimens.xml/attrs.xml/styles.xml/drawable/*.png/*.jpg/*.gif/*.9.png/*.xml/anim/*.xml`/layout/*.xml/raw/*.*/xml/*.xml/assets/*.*/*.*

Wewillcoverattrs.xmlandstyles.xmlinChapter7.Thexmlfilesintheanimsubdirectorydefineanimationsthatcanbeappliedtovariousviews.Wewillcovertheseanimation-relatedresourcesintheanimationschapter(Chapter18).ThexmlfilesinthexmlsubdirectorygetcompiledawaytobinaryandtheirresourceIDscanbeusedtoread

them.Wewillshowanexampleofthisshortly.The/rawsubdirectoryholdsfilesthatgetplaced,astheyare,withoutgettingconvertedtoanybinaryformat.

The/assetsdirectory,whichisasiblingof/res,isnotpartoftheresourcehierarchy.Thismeansthatthefilesinthissubdirectorydonotchangebasedonlanguageoralocale.AndroiddoesnotgenerateanyIDsforthesefiles.Thisdirectoryismorelikeastaticlocalstorageforanyfilesthatareusedasinputs,suchasconfigurationfilesforyourapplication.

Exceptfortheassetsdirectory,everyotherartifactinthe/ressubdirectorywillendupgeneratinganIDinR.*namespace,asyouhaveseenbefore.EachdistinctresourcetypewillhaveitsownnamespaceunderR.*,asinR.id,R.string,orR.drawable,etc.

ReadingResourcesfromJavaCodeInlayoutfiles,asyouhaveseentheincalculatorlayout,oneresourcecanrefertootherresources.Forexample,thecalculatorlayoutresourcefilereferencedthestringandcolorreferences.Thisapproachiscommon.Alternatively,youcanalsouseJavaAPItoretrievetheresourcevaluesusingthemethodActivity.getResources().ThismethodreturnsareferencetotheAndroidSDKjavaclassresources.YoucanusemethodsonthisclasstogettothevaluesofeachresourceidentifiedinyourlocalR.*namespace.Listing2-11showsanillustrationofthisapproach:

Listing2-11.ReadingResourceValuesinJavaCode

Resourcesres=activity.getResources();//Retrievingacolorresourceintsomecolor=res.getColor(R.color.main_back_ground_color);//UsingadrawableresourceColorDrawableredDrawable=(ColorDrawable)res.getDrawable(R.drawable.red_rectangle);

RuntimeBehaviorofDrawableResourcesThedrawabledirectoryisaninterestingcaseworthcoveringtodemonstratethefluencyofAndroid’sarchitecture.Asshownearlier,thisdirectorycancontainimagesthatcanbesetasbackgrounds.ThisdirectoryalsoallowsXMLfilesthatknowhowtogetconvertedtodrawablejavaobjectswhichcanthenbeusedasbackgroundsthatarerenderedatruntime.Listing2-12showsanexampleofthis:

Listing2-12.ExampleofaShapeDrawableXMLResourceFile

<?xmlversion="1.0"encoding="utf-8"?><shapexmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle">

<solidandroid:color="#f0600000"/><strokeandroid:width="3dp"android:color="#ffff8080"/><cornersandroid:radius="13dp"/><paddingandroid:left="10dp"android:top="10dp"android:right="10dp"android:bottom="10dp"/></shape>

Ifyouplaceafilelikethisinthedrawablesubdirectoryandcallitbackground1.xml,itwillresultinanIDcalledR.drawable.background1.YoucanthenusethatIDasifitwereabackgroundimageforanyviewthatisdrawnwitharectangularborder.Otherpossibleshapesareovals,lines,andrings.

Similartotheshapexmlfile,eachallowedXMLfileinthedrawabledirectorydefinesadrawablethatdefinesaparticularwaytodraw.Examplesofthesedrawablesincludebitmapsthatcanbedecoratedwithcertainbehavior,orimagesthatcantransitionfromoneimagetoanother,layereddrawablesthatarecollectionsofotherdrawables,drawablesthatcanbeselectedbasedoninputparameters,drawablesthatcanrespondtoprogressbyshowingmultipleimages,drawablesthatcanclipotherdrawables,etc…SeethefollowingURLforanumberofsophisticatedthingsyoucandousingtheseruntimedrawableobjects:

http://androidbook.com/item/4236

UsingArbitraryXMLFilesasResourcesAndroidalsoallowsarbitraryXMLfilestobeusedasresourceswhichcanthenbelocalizedortunedforeachdevice.Listing2-13isanexampleofreadingandprocessinganXML-basedresourcefilefromthe/res/xmlsubdirectory.

Listing2-13.ReadinganXMLResourceFile

privateStringreadAnXMLFile(Activityactivity)throwsXmlPullParserException,IOException{StringBuffersb=newStringBuffer();Resourcesres=activity.getResources();XmlResourceParserxpp=res.getXml(R.xml.test);

xpp.next();inteventType=xpp.getEventType();while(eventType!=XmlPullParser.END_DOCUMENT){if(eventType==XmlPullParser.START_DOCUMENT){sb.append("******Startdocument");}elseif(eventType==XmlPullParser.START_TAG){sb.append("\nStarttag"+xpp.getName());}elseif(eventType==XmlPullParser.END_TAG){sb.append("\nEndtag"+xpp.getName());

}elseif(eventType==XmlPullParser.TEXT){sb.append("\nText"+xpp.getText());}eventType=xpp.next();}//eof-whilesb.append("\n******Enddocument");returnsb.toString();}//eof-function

WorkingwithRawResourceFilesAndroidalsoallowsanytypeofnon-compiledfilesasresources.Listing2-14isanexampleofreadingafilethatisplacedinthe/res/rawsubdirectory.Beingaresourceeventherawfilesthatareinthisdirectorycanbecustomizedforlanguageoradeviceconfiguration.AndroidgeneratesIDsautomaticallyforthesefilesaswell,astheyareresourceslikeanyotherresource.

Listing2-14.ReadingaRawResourceFile

StringgetStringFromRawFile(Activityactivity)throwsIOException{Resourcesr=activity.getResources();InputStreamis=r.openRawResource(R.raw.test);//assumingyouhaveafunctiontoconvertastreamtoastringStringmyText=convertStreamToString(is);is.close();//takecareofexceptionsetc.returnmyText;}

ReadingFilesfromtheAssetsDirectoryAlthoughusuallyclubbedwithresources,the/assetsdirectoryisabitdifferent.Thisdirectorydoesnotsitunderthe/respath,sothefilesinthisdirectorydonotbehavelikeresourcefiles.AndroiddoesnotgenerateresourceIDsforthesefilesintheR.*namespace.Thesefilesarenotcustomizablebasedonlocaleordeviceconfiguration.Listing2-15showsanexampleofreadingafilethatisplacedinthe/assetssubdirectory.

Listing2-15.ReadingaFilefromAssetsDirectory

StringgetStringFromAssetFile(Activityactivity){AssetManageram=activity.getAssets();InputStreamis=am.open("test.txt");Strings=convertStreamToString(is);is.close();

returns;}

Thusfar,wehaveusedanactivityreferencetogetholdoftheresourcesoranAssetManagerobjectasinListing2-15.Inreality,allweneedisthebaseclassoftheactivity,thecontextobject.

ReadingResourcesandAssetsWithoutanActivityReferenceSometimesyoumayneedtoreadanXMLresourcefileoranassetfilefromthebowelsofsourcecode,whereitisintrusivetopasstheactivityreference.Forthesecases,youcanusethefollowingapproachtoobtaintheapplicationcontextandthenusethatreferenceinsteadtogettotheassetsandresources.

WhenAndroidloadsyourapplication(inordertoinvokeanyofitscomponents),itinstantiatesandcallsanapplicationobjecttoinformthattheapplicationcouldinitializeitself.ThisapplicationclassnameisspecifiedintheAndroidmanifestfile.IfMyApplication.javaisyourapplicationjavaclass,thenitcanbespecifiedintheAndroidmanifestfileasshowninListing2-16.

Listing2-16.SpecifyinganApplicationClassintheManifestFile

<applicationandroid:name=".MyApplication"android:icon="@drawable/icon".../>

Listing2-17showshowwecancodetheMyApplicationandalsoshowshowwecancapturetheapplicationcontextinaglobalvariable.

Listing2-17.SampleCodeforanApplicationthatCapturesApplicationContext

publicclassMyApplicationextendsApplication{//MakesuretocheckfornullforthisvariablepublicstaticvolatileContexts_appContext=null;

@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){super.onConfigurationChanged(newConfig);}@OverridepublicvoidonCreate(){super.onCreate();MyApplication.s_appContext=this.getApplicationContext();}@OverridepublicvoidonLowMemory(){

super.onLowMemory();}@OverridepublicvoidonTerminate(){super.onTerminate();}}

Withtheapplicationcontextcapturedinaglobalvariable,wenowcangettotheassetmanagertoreadourassetsasinListing2-18.

Listing2-18.UsingApplicationObjecttoGettoApplicationAssetFiles

AssetManageram=MyApplication.s_appContext.getAssets();InputStreamis=am.open(filename);

UnderstandingResourceDirectories,Language,andLocaleLet’swrapuptheideaofAndroidresourcesbypointingouthowresourcedirectoriesareusedtoloadresourcesbasedonlanguage,locale,oraconfigurationchangeofthedevicelikesayorientation.SeehowinListing2-19alayoutfilewiththesamenameislocatedinmultiplelayoutdirectoriesstartingwithsameprefixoflayoutbutwithdifferentqualifierssuchas“port”forportraitand“land”forlandscape.TherearealargenumberofthesequalifiersavailableintheSDKdocumentation.WealsocoversomeoftheseaspectsinChapter9(ConfigurationChanges).Listing2-19showsanexampleofhowlayoutfilesarearrangedbyportraitorlandscapeconfiguration:

Listing2-19.DemonstratingResourceQualifiers

\res\layout\main_layout.xml\res\layout-port\main_layout.xml\res\layout-land\main_layout.xml

MoreonIntentsWehavetalkedabouthowintentsareusedtoinvokeactivities.Wewantcoverfewmoreessentialaspectsofintentsnow.Listing2-20showshowintentsareusedtoinvokeanumberofprebuiltGoogleapplications.

Listing2-20.SampleCodeUsingIntents

publicclassIntentsUtils{publicstaticvoidinvokeWebBrowser(Activityactivity){Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.google.com"));

activity.startActivity(intent);}publicstaticvoidinvokeWebSearch(Activityactivity){Intentintent=newIntent(Intent.ACTION_WEB_SEARCH);intent.setData(Uri.parse("http://www.google.com"));activity.startActivity(intent);}publicstaticvoiddial(Activityactivity){Intentintent=newIntent(Intent.ACTION_DIAL);activity.startActivity(intent);}publicstaticvoidcall(Activityactivity){Intentintent=newIntent(Intent.ACTION_CALL);intent.setData(Uri.parse("tel:555–555–5555"));activity.startActivity(intent);}publicstaticvoidshowMapAtLatLong(Activityactivity){Intentintent=newIntent(Intent.ACTION_VIEW);//geo:lat,long?z=zoomlevel&q=question-stringintent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));activity.startActivity(intent);}}

Noticehowtheseintentsdonotinvokeaspecificactivitybyitsclassnamebutratherusethetargetqualitiesofsuitableactivities.Forexampletoinvokeabrowsertoviewawebpage,theintentsimplysaystheactionisACTION_VIEWandthedataportionoftheintentissettothewebaddress.Androidthenlooksaroundtoseealltheactivitiesthatknowhowtoshowthetypeofdatarequestedinthedataattribute.ItwillthengivetheuseranoptionwhichoftheactivitiesthattheuserwantstochoosetoopentheURL.Thesetypesofintentsthatdon’tspecifytheclassnameofthecomponenttoinvokearecalledimplicitintents.Wewillcoverthisinalittlebitmoredetailshortly.

StartingActivitiesforResultsListing2-21showsanexampleofanactivitywhereoneofitsmethodsisinvokingatargetactivityinordertoobtainaresultwhenthattargetactivityiscompleted.ThisisdonethroughthemethodinvokePick()inasshowninListing2-21.

Listing2-21.UsingIntentstoGetResultsfromActivities

publicclassSomeActivityextendsActivity{.....//Callthismethodtostartatargetactivitythatknowshow

topickanote//UseadataURIthattellsthetargetactivitywhichlistofnotestoshowpublicstaticvoidinvokePick(Activityactivity){

IntentpickIntent=newIntent(Intent.ACTION_PICK);intrequestCode=1;pickIntent.setData(Uri.parse("content://com.google.provider.NotePad/notes"));activity.startActivityForResult(pickIntent,requestCode);}

//thefollowingmethodwillbecalledwhenthetargetactivityfinishes//NoticetheoutputIntentobjectthatispassedbackwhichcould//containadditionalinformation

@OverrideprotectedvoidonActivityResult(intrequestCode,intresultCode,IntentoutputIntent){

super.onActivityResult(requestCode,resultCode,outputIntent);parseResult(this,requestCode,resultCode,outputIntent);}publicstaticvoidparseResult(Activityactivity

,intrequestCode,intresultCode,IntentoutputIntent){if(requestCode!=1){Log.d("Test","Someoneelsecalledthis.notus");return;}if(resultCode!=Activity.RESULT_OK){Log.d("Test","Resultcodeisnotok:"+resultCode);return;}Log.d("Test","Resultcodeisok:"+resultCode);UriselectedUri=outputIntent.getData();Log.d("Test","Theoutputuri:"+selectedUri.toString());

//ProceedtodisplaythenoteoutputIntent.setAction(Intent.ACTION_VIEW);startActivity(outputIntent);}

TheconstantsRESULT_OK,RESULT_CANCELED,andRESULT_FIRST_USERarealldefinedintheactivityclass.TheconstantRESULT_FIRST_USERisusedasa

startingnumberforuser-definedactivityresults.ThenumericalvaluesoftheseconstantsareshowninListing2-22:

Listing2-22.ResultValuesfromReturnedActivities

RESULT_OK=-1;RESULT_CANCELED=0;RESULT_FIRST_USER=1;

TomakethePICKfunctionalitywork,theimplementingorthetargetactivitythatisrespondingshouldhavecodethatexplicitlyaddressestheneedsofanACTION_PICK.Let’slookatanexampleofhowthisisdoneintheGooglesampleNotePadapplication.(Seethereferencessection,whereyoucanfindthisapplication.)Whentheitemisselectedinthelistofitems,theintentthatinvokedthetargetactivityischeckedtoseewhetherit’sanACTION_PICKintent.Ifitis,thedataURIoftheselectednoteitemissetinanewintentandreturnedthroughsetResult()asshowninListing2-23.Thecallingactivitythencaninvestigatethereturnedintenttoseewhatdataithasinit.SeethemethodparseResult()inListing2-21.

Listing2-23.TargetActivityReturningaResultthroughaDataURI

@OverrideprotectedvoidonListItemClick(ListViewl,Viewv,intposition,longid){Uriuri=ContentUris.withAppendedId(getIntent().getData(),id);

Stringaction=getIntent().getAction();if(Intent.ACTION_PICK.equals(action)||Intent.ACTION_GET_CONTENT.equals(action)){//Thecalleriswaitingforustoreturnanoteselectedby//theuser.Theyhaveclickedonone,soreturnitnow.setResult(RESULT_OK,newIntent().setData(uri));finish();}...otherwaysofhowthisactivitymayhavebeeninvoked}

ExercisingtheGET_CONTENTActionACTION_GET_CONTENTissimilartoACTION_PICK.InthecaseofACTION_PICK,youarespecifyingadataURIthatpointstoacollectionofitems,likealistofnotesfromaNotePad-likeapplication.Youwillexpecttheintentactiontopickoneofthenotesandreturnittothecaller.InthecaseofACTION_GET_CONTENT,youindicatetoAndroidthatyouneedanitemofaparticularMIMEtype.Androidsearchesforactivitiesthatcaneithercreateoneofthoseitemsorchoosefromanexistingsetofitemsthatsatisfythat

MIMEtype.

UsingACTION_GET_CONTENT,youcanpickanotefromacollectionofnotessupportedbytheNotePadapplicationusingthecodeshowninListing2-24:

Listing2-24.InvokingActivitiesforCreatingContent

publicstaticvoidinvokeGetContent(Activityactivity){IntentpickIntent=newIntent(Intent.ACTION_GET_CONTENT);intrequestCode=2;pickIntent.setType("vnd.android.cursor.item/vnd.google.note");activity.startActivityForResult(pickIntent,requestCode);}

NoticehowtheintenttypeissettotheMIMEtypeofasinglenote.ContrastthiswiththeACTION_PICKcode,whereitexplicitlyindicatedaURLthatpointstoacollectionofnotes(likeawebURLthatcanretrieveapageworthofdata).

ForanactivitytorespondtoACTION_GET_CONTENT,theactivityhastoregisteranintentfilterindicatingthattheactivitycanprovideanitemofthatMIMEtype.Listing2-25showshowtheSDK’sNotePadapplicationaccomplishesthis:

Listing2-25.ActivityFilterforGetContent

<activityandroid:name="NotesList"android:label="@string/title_notes_list">......<intent-filter><actionandroid:name="android.intent.action.GET_CONTENT"/><categoryandroid:name="android.intent.category.DEFAULT"/><dataandroid:mimeType="vnd.android.cursor.item/vnd.google.note"/></intent-filter>......</activity>

TherestofthecodeforrespondingtoonActivityResult()isidenticaltothepreviousACTION_PICKexample.IftherearemultipleactivitiesthatcanreturnthesameMIMEtype,Androidwillshowyouthechooserdialogtoletyoupickanactivity.

RelatingIntentsandActivitiesAnintentisusedtostartnotonlyactivitiesbutalsoothercomponentslikeaserviceorabroadcastreceiver.Thesecomponentsarecoveredinlaterchapters.Youcanseethese

componentsashavingcertainattributes.Oneattributeofacomponentmaybethecategorytowhichthiscomponentbelongs.Anotherattributemaybewhattypeofdatathiscomponentcanview,edit,update,ordelete.Anotherattributemaybewhattypeofactionsacomponentcanrespondto.Ifyouweretolookuponthesecomponentsasentitiesinadatabase,theirattributescanbeseenascolumns.Thenanintentcanbeseenasawhereclausethatspecifiesallorsomeofthosecharacteristicstochooseacomponentlikeanactivitytostart.Listing2-26isanexampleofdemonstratinghowtoqueryforallactivitiesthatarecategorizedasCATEGORY_LAUNCHER.

Listing2-26.QueryingActivitiesthatMatchanIntent

IntentmainIntent=newIntent(Intent.ACTION_MAIN,null);mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);PackageManagerpm=getPackageManager();List<ResolveInfo>list=pm.queryIntentActivities(mainIntent,0);

PackageManagerisakeyclassthatallowsyoutodiscoveractivitiesthatmatchcertainintentswithoutinvokingthem.Youcancyclethroughthereceivedactivitiesandinvokethemasyouseefit,basedontheResolveInfoAPI.Listing2-27isanextensiontotheprecedingcodethatwalksthroughthelistofactivitiesandinvokesoneoftheactivitiesifitmatchesaname.Inthecode,wehaveusedanarbitrarynametotestit:

Listing2-27.WalkingThroughaMatchedActivityListforanIntent

for(ResolveInfori:list){//ri.activityInfo.Log.d("test",ri.toString());Stringpackagename=ri.activityInfo.packageName;Stringclassname=ri.activityInfo.name;Log.d("test",packagename+":"+classname);if(classname.equals("com.ai.androidbook.resources.TestActivity")){Intentni=newIntent();ni.setClassName(packagename,classname);activity.startActivity(ni);}}

UnderstandingExplicitandImplicitIntentsWhenyouspecifyanexplicitactivityname(oracomponentnamelikeaserviceorabroadcastreceiver)inanintent,suchanintentiscalledanexplicitintent.Whenthisintentisusedtostartanactivity,thatactivityisinvokedirrespectiveofwhatelseisthereinthatintentsuchasitscategoryordata.

Asyouhaveseen,anintentdoesnothavetohaveanactivityspecifiedexplicitlytoinvoke

it.Anintentcanrelyonanactivity’sactionattribute,categoryattribute,ordataattribute.Theseintentsthatomittheexplicitactivityorcomponentclassarecalledimplicitintents.WhenyouuseanimplicitintenttoinvokeanactivityitisparamountthattheactivitymusthaveasoneofitscategoriesCATEGORY_DEFAULT.Ifyouexpectyouractivitytobeexplicitlystartedbyanintent,thenyoudon’tneedtospecifyanycategoryatalltothatactivity.Listing2-28showsanexampleofminimallyregisteringanactivityinanAndroidmanifestfilesothatitcanbeinvokedbyanexplicitintent.

Listing2-28.MinimalActivityDefinition

<activityandroid:name="com.androidbook.asynctask.TestProgressBarDriverActivity"android:label="TestProgressbars"/>

Ifyouwanttoinvokethisactivitythroughanimplicitintentwithoutspecifyingitsclassname,likethroughanactionsay,thenyouneedtoaddthefollowingintentfilters,onefortheactionandonefortheneededmandatorycategoryofdefault,asshowninListing2-29.

Listing2-29.AnActivityDefinitionwithFilters

<activityandroid:name="com.androidbook.asynctask.TestProgressBarDriverActivity"android:label="TestProgressbars"><intent-filter><actionandroid:name="com.androidbook.intent.action.ME"/><categoryandroid:name="android.intent.category.DEFAULT"/></intent-filter></activity>

SavingStateinAndroidAsyoureviewedthecalculatorapp,yournextlikelyneedishowtostorethedataofanAndroidapp.Let’sbrieflycovertheavailableoptions.TherearefivewaystostoredatainAndroid:1)sharedpreferences,2)internalfiles,3)externalfiles,4)SQLlite,and5)networkstorageinthecloud.

SharedpreferencesAPIisasophisticatedAPIintheAndroidSDKtosave,display,andmanipulatepreferencesforyourapplications.Althoughthisfeatureisintendedandtailoredforpreferencesitcanbeusedforsavingarbitrarystateofyourapplication.Sharedpreferencesareinternaltotheapplicationanddevice.Androiddoesnotmakethisdataavailabletootherapplications.AuserisnotexpectedtodirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.ThesesharedpreferencesarecoveredindetailinChapter11.

Whilesharedpreferencesdataisstructuredkey/valuepairdataandfollowsafewothersemanticsimposed,internalfilesarestand-alonefilesthatyoucanwritetowithouta

predefinedstructure.Wehaven’tfoundacompellingadvantageofusinginternalfilesoversharedpreferences,ortheotherway,especiallyforsmall-tomedium-sizedstate.Soformostappsyoucanchooseoneortheother.

Unlikeinternalfiles,whicharestoredontheinternalstorageofthedevice,externalfilesarestoredontheSDcard.Thesebecomepublicfilesthatotherappsincludingtheusercouldseeoutsidethecontextofyourapplication.Theexternalfilescanbeusedtostoredatathatmakessenseevenoutsideofyourappsuchasimagefilesorvideofiles.Forstrictlytheinternalstateoftheapp,internalfilesareabetteroption.

Theexternalfilesmayalsobeanoptionifthestateisverylargerunningintotensofmegabytes.Usuallywhenthathappensyoudon’twanttosavethestateasamonolithicfileanywayandoptformoregranularstorageasarelationaldatabaseliketheSQLlite.

WewillgiveaquickoverviewandbriefcodesamplesinChapter25onhowtousepreferences,internalfiles,andexternalfilestostoreyourappstate.OneofthetricksistopersistjavaobjecttreedirectlyusingJSONandGSOnwhilegivingconsiderationtoseeifthislevelofgranularityisappropriate.IfyouarenotfamiliarwithJSON,itisanobjecttransportandstorageformatforJavaScript-basedobjects.Itisalsogenerallyapplicableanyobjectstructureaswellincludingjavaobjectsandoftenusedthatwaylately.TheGSONisaGooglelibrarythatconvertsJavaobjectstoandfromJSONstrings.

SQLliteisareallygoodoptionthatisrecommendedtostorethestateofanapp.Theshortdrawbackisyourlogictosaveandreaddatabecomeverboseandcumbersome.YoucanprobablyuseO/Rmappinglibrariestoovercomethismismatchbetweenjavaobjectsanditsrelationalrepresentation.SQLliteisalsooftenusedtostoredatathatneedstobesharedbymultipleapplicationsthroughaconceptcalledcontentproviders.ThisisthecentraltopicofChapter25.

Finally,cloud-basednetworkstorageiscomingintoitsown.ForexampleanumberofMBAAS(MobileBackendasaService)platformssuchasparse.comsupportstoringthemobiledatadirectlyinthecloudforbothonlineandofflineusage.Thismodelisgoingtobeincreasinglyrelevantasyoustartmakingyourappavailableonmultipledevicesforthesameuserorbeingabletocollaboratewithotherusers.ThistopiciscoveredingreatdetailinourcompanionbookExpertAndroidfromApress.

ManytimeforyourappstheGSONoptiontostoreappstateinaninternalfileisreallythequickestandmostpracticalwaytogo.Ofcourseyoudowanttoanalyzethegranularityofthesolutionandseeifthissimplerapproachwon’tbecomeaburdenoncomputingpowerorbatterylife.IfyourappgainslotofpopularityyoumaywanttouseasecondreleasewithSQLlitebyoptimizingstoragespeedorusecloudstorageifthatismoreappropriateforthatrelease.

RoadmapforLearningAndroidandtheRestoftheBookLet’squicklyreviewwhatwehavecoveredsofar.Intheone-pagerapplicationyouhaveseenhowtheUIisputtogether,howthebusinesslogiciscodedinJava,andthenhowthe

applicationisdefinedtotheAndroidsdkusingtheAndroidmanifestfile.Weexplainedwhatresourcesare,howtheyreferenceeachother,howtheyarereferencedinlayoutfiles,andevenhowtoreadyourinputfilesasresources.Wehaveshownyouwhatintentsare,theirintricacies,andhowtousethemtoinvokeordiscoveractivities.Wehavecoveredtheactivity’slifecycle,whichisreallyimportanttounderstandAndroidarchitecture.Wehavealsogivenaquickrundownofhowyoucansavethestateofyourapplication.Thisisaprettygoodfoundationtoplanandwritesimpleapplications.

Wenowwanttofollowupthisbird’seyeviewofAndroidapplicationswitharoadmapofbecominganexpertappdeveloperontheAndroidplatform.Thisroadmapdividesthechaptersofthisbookintothefollowingsixkeylearningtracks:

Track1:UIessentialsforyourAndroidapplications

Track2:Savingstate

Track3:Preparing/takingyourapplicationtoGooglePlay

Track4:Makingyourapplicationrobust

Track5:Bringingfinessetoyourapps

Track6:Integratingwithotherdevicesandthecloud

Amongthesesixtracks,thefirstthreearethebasictracksthatyoumustknowwelltowriteAndroidappsthatareusefultoyouandthelargercommunity.Tracks4,5,and6aretheretomakeyourappsbetterandfeaturerichinsubsequentreleases.Wewilltalkaboutwhatchaptersmakeupeachtrackandwhatyouareexpectedtogainfromthattrack.

Track1:UIEssentialsforYourAndroidApplicationsAndroidhasanumberofUIcontrolsandlayoutsoutoftheboxtowriteveryfeaturerich-applications.Someexamplesarebuttons,variousTextViews,EditTextcontrols,checkboxes,RadioButtons,dateandtimecontrols,listcontrols,controlstoshowanaloganddigitalclocks,controlstoshowimagesandvideos,controlstopicknumbers,etc.WewillcoveranumberoftheseinChapter3.Inthatchapter,wewillalsocovertheessentiallayoutsthatareneededtocomposetheUIfromthosecontrols.

OnceyouareabletousethebasiccontrolstoconstructyourUI,theonecontrolthatyouabsolutelyneedinyourappsisthelistcontrol.Wedidnotcoverlistcontrolasabasiccontrolbecauseitisabitinvolved.AlsoAndroidhasanumberoffeaturesandapproachestodolist-basedapplications.Sowehavededicatedaseparatechapterforlistcontrolsandthedataadaptersthatarenecessarytopopulatethoselistcontrols.TheseaspectsarecoveredinChapter4.

Onceyoumasterthebasiccontrols,basiclayouts,andlistcontrols,youwillstartlookingaroundformoresophisticatedlayoutslikethegridandtablelayouts.ThesearecoveredinChapter5under“UsingAdvancedLayouts.”

MenusarecoveredinChapter6.Android’smenuinfrastructureincludescontextmenus,

pop-upmenus,optioniconsinanactionbar,etc.

Yourmobileappisnotreallycompletewithoutrefiningitthroughstyling,muchlikeCSS.Chapter7covershowstylesandthemesworkinAndroid.

DialogsareessentialinanyUI.DialogsareabitinvolvedinAndroid.TounderstanddialogsinAndroidyouhavetofirstunderstandtheconceptoffragments.Architectureofdialogsisonlyoneaspectoffragments.FragmentsarenowcoretotheAndroidUI.Chapter8explainswhatfragmentsareandinChapter10wecoverdialogs,buildinguponChapter8.

Inmobileapps,youcannotwriteanappwithoutunderstandingwhathappenstoyourapplicationwhenthedeviceorientationchanges.ProgrammingcorrectlyfororientationchangeisnottrivialinAndroid.HowtoprogramfororientationandotherdeviceconfigurationchangesiscoveredinChapter9.

ForanyreasonablyusefulapplicationyouwilllikelyneedtoknowalltheseUIessentials.SoTrack1isanessentialtrack.

Track2:SavingStateOnceyouknowhowtoconstructtheUIofyourapplication,thenextneedyouwillrunintoistosavethestateofyourapplication.Refertotheearliersectiononsavingstatetoseewhatoptionsareavailableandinwhichchaptersthoseoptionsarecovered.Track2isalsoanessentialtrackasyoushouldknowhowtosavestate.

Track3:Preparing/TakingYourApplicationtotheMarketBycompletingTracks1and2youcanbuildaprettyreasonableapplicationthatyoucandeploytothemarketplace.Chapter30showsyouhowyoucantakeyourapptotheGooglePlaystore.

Track4:MakingYourApplicationRobustTrack4isanadvancedtrackgettingintotheinternalsofAndroid.YouwillneedtogothroughthechaptersinthistracktosolidifyyourunderstandingofhowAndroidworks.WestartthistrackwithChapter12oncompatibilitylibrary.Thischapterteacheshowtomakeyourapprunwellonolderreleaseswhileusingfeaturesthatareavailableonlyonthenewerplatforms.

Androidallowsyoutoruncodeinyourapplicationeventhoughyouarenotactivelyusingtheapplicationintheforeground.Itcouldbethemusicyouareplayinginthebackground,oritcouldbebackingupyourimagestoacloud,etc.ThistypeofcodeiscalledaServiceinAndroid.WorkingwithservicesiscoveredinChapter13.Theseservicescanbetriggeredbyadirectuseractionorthroughalarmsorbroadcastevents.AlarmmanageriscoveredinChapter17.

Whenyouuseintentstoinvokecomponentssuchasactivitiesorservicesyouaretargetingasinglecomponent.Androidalsosupportsapublish-and-subscribeprotocolwhereanintentcanbeusedtoinvokemultiplecomponentsthatregisterforitatthesametime.Thesecomponentsarecalledreceiversorbroadcastreceivers.Abroadcastreceiverisapieceofcodeinyourapplicationthatisexecutedinresponsetoabroadcastedeventevenifyourapplicationhadnotbeenstartedorwasjustdormantatthetimeoftheevent.HowtoworkwithbroadcastreceiversiscoveredinChapter16.

AsyoustartusingmoreandmorefeaturesofAndroidsuchasservices,broadcastreceivers,andcontentprovidersyouwillneedtounderstandhowAndroidusesasinglemainthreadtorunthecodeinthesecomponents.ThisthreadingmodeliscoveredindetailinChapter14.Knowingthiswillhelpyouwritecodethatisrobust.Inthistrack,youwillalsolearnabouttheveryusefulAsyncTask,whichisusedtosimplifyoffloadingworkfromthemainthread.ThisAPIisoftenusedfromUItoreadmessagesfromtheweborcheckfore-mails,etc.AsyncTaskiscoveredinChapter15.

Track5:BringingFinessetoYourAppsTomakeyourappslookappealing,oneofthefirstthingsyoucandoistoaddalittleoralotofanimation.ThisiscoveredinChapter18.Touch-basedinterfacesarenowthenorm.Manipulatingyourenvironmentwithdraganddropismorenatural.Youwanttoemploysensorstowriteappsthatintegratewiththeexternalworldbetter.Thesetouchscreens,draganddrop,andsensorsarerespectivelycoveredinChapters22,23,and24.

Homescreenwidgetsareawonderfulwaytoextractpiecesofyourappandmakeitavailableonanyhomescreenofyourchoosing.Thispersonalizationfeature,whenusedinnovativelywithvalueinmind,makestheinteractionwiththedevicesimpleandjoyful.WidgetsarecoveredinChapter21.

Map-andlocation-basedappsaremadeformobiledevices.ThistopiciscoveredinChapter19.

YoucanveryeasilyintegrateaudioandvideointoyourappsonAndroid.ThisAPIiscoveredinChapter20.

Track6:IntegratingwithOtherDevicesandtheCloudYoucanuseGooglecloudmessagingtoreachouttotheusersofyourmobileapplications.GooglecloudmessagingiscoveredinChapter29.WithNFCandBluetoothcapabilitiesinAndroidyoucanstartinteractingwithyourphysicalenvironmentinyourapps.Wehopetopostsomematerialonthesetopicstotheonlinecompanionforthebook.

FinalTrack:GettingaHelpingHandfromExpertAndroid

Now,wearegoingtotalkaboutafewtopicsthatarenotcoveredinthisbook.Youmaywanttoconsiderthesetopics,shouldyoufindthemrelevanttoyourneeds.MostofthesearebasedonourresearchfortheExpertAndroidbookthatwepublishedinearly2014throughApress.

AndroidhasapublicAPItowritecustomcomponentsthatcanworkandbehavedifferentlythanwhatcomeoutofthebox.Youcanwritecustomviewswhereyoucancontrolwhattodrawandhowtodraw,whichcanthencoexistwithothercontrolsthatareoutoftheboxlikeabuttonoratextcontrol.Youcanalsocombinemultipleexistingcontrolsintoacompoundcontrolthatcanthenbehavelikeanindependentcontrol.Youcanalsodesignnewlayoutsthatsuityourdisplayneeds.Therearealotoftrickythingstocreatethesecustomcomponentswell.YouhavetounderstandthecoreAndroidviewarchitecture.Thismaterialiscoveredoverthreechaptersand100pagesintheExpertAndroidbookfromApress.

Ifyourappsareformbased,youwillneedtowritealotofcodetovalidatetheforminput.Youreallyneedaframeworktohandlethis.ExpertAndroidhasachapteroncreatingasmallformprocessingframeworkthatisreallyusefulandwillreduceerrorsandtheamountofcodeyouneedtowrite.

MBAAS,MobileBackendasaService,isaneededtechnologyformobileappsandisnowprettywidelyavailable.ThefacilitiesanMBAASoffersareuserlogins,sociallogins,usermanagement,savinddataonbehalfofusersinthecloud,communicationwiththeusers,collaborationbetweentheusersthemselves,etc.InExpertAndroidwehavemultiplechaptersdedicatedtoanMBAASplatformcalledParse.

OpenGLhascomealongwayonAndroidwithnow-substantialsupportforthenewgenerationofprogrammableGPUs.AndroidhasbeensupportingES2.0forsometimenow.InExpertAndroidwehaveover100pagesofcoverageonOpenGL.Westartatthebeginningandexplainalltheconceptswithoutneedingtorefertoexternalbooks,althoughwedogiveanextensivebibliographyonOpenGL.WecoverES2.0reallywellandprovideguidancetocombineOpenGLandregularviewstopavethewayfor3Dcomponents.

FederatedsearchprotocolofAndroidispowerfulasyoucanuseitinquiteafewimaginativeways.ExpertAndroidfullyexploresitsfundamentalsandalsosomealternatewaysofusingitoptimally.

Androidprovidesanincreasinglylargesetoffeaturesfordebugging.ThesetopicsarecoveredinExpertAndroid.Acellphoneisultimatelyatalkingdevice,althoughitisusedlessandlessoftenforthat.WehaveachapteronutilizingthetelephonyAPIinExpertAndroidaswell.

AsWeLeaveYouNowwiththeRestoftheBookFinally,youmaybewonderingwhyyoushouldevenbecomeamobiledeveloper.Wecancitetwostrongarguments,oneofwhichneverexistedbefore.Thefamiliaroneistobe

partofanITorganizationfortheirmobileprogrammingefforts.TheITopportunitiesareontherisebutnotfullyrealizedyetunlikewhathappenedwiththeWebprogrammingparadigmwhenitcameintobeing.Weexpectthisneed,however,tobeagraduallyincreasingdemand.

Ontheotherhand,theimmediateandexcitingopportunityisforyoutobecomeanindependentapppublisher.Theavailabilityofasaleschannelfortheappsthatyouwriteisauniqueoneinthesoftwareindustry.NoteveryoneofusisgoingtobearisingstarinanITorganization.Theindependentdeveloperpathgivesanavenueforyoutogrowatyourownpaceandinadirectionthatsatisfiesyou.Luckandpatiencemightevenmakeyourich.Atleastyoucanaddvaluetothesocietywhilemeetingyourneeds.

ShouldyoudecidetoventureintotheAndroidmobileprogrammingspace,youwanttobepreparedwiththerighthardwarethatmakesthisexperiencebearable.IfyouarebuyingaWindowslaptopseeifyoucangetonewithatleast8Gofmemory,solid-stateharddrive,andareasonablyfastprocessor.Expecttospendabout$1,000to$1,500.IfyouarebuyingaMaclaptop,asimilarconfigurationmaycostyouabout$2,500.AgoodfastconfigurationisimportantforAndroiddevelopment.IfyouareaseasonedJavaprogrammer,giventhisinvestment,andthisbookinhand,ifyoufollowthetrackslaidouthereyoucanbecomeacompetentmobileAndroidappdeveloperinaboutsixmonths.

ReferencesHereareadditionalresourcesforthetopicsdiscussedinthischapter.

http://androidbook.com/free-android-chapters:YoucanusethisURLtodownloaddetailedchaptersonresourcesandintents(madeavailablefreefrompreviouseditionsduetospacelimitations).

http://androidbook.com/working-with-avds:YouwillfindatthisURLnotesoninstallingAndroid,workingwithAVDs,signingAPKfiles,andmoretogetyoustartedwithAndroid.

http://androidbook.com/item/3574:ThisURLshowshowtorunanAndroidapplicationonadevicefromtheeclipseADT.ThislinkalsoshowsyouhowtohookupyourdevicethroughaUSBporttoyourdevelopmentcomputer.

http://androidbook.com/item/4629:ThisURLtalksaboutkeycallbackfunctionsonanactivity.Monitoringtheactivitycallbacksisagoodwaytogetahandleontheactivitylifecycle.Youcancopythecodefromheretocreateabaseactivitythatcanmonitorandlogthesecallbacksforyou.

http://androidbook.com/item/4440:ThisURLtalksabouthowyoucanuseGSONandJSONforpersistenceneedsofyourapplication.Thisarticlesuggestsaneasywaytopersistdataon

thedeviceforyourapps.

ExpertAndroidfromApresstalksaboutpassingobjectsthroughAndroidbundlesasparcelablesindepth.

http://developer.android.com/guide/topics/resources/index.htmlAndroidSDKroadmaptothedocumentationonresources.

http://developer.android.com/guide/topics/resources/available-resources.html:Androiddocumentationofvarioustypesofresourcesavailable.

http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources:AlistofvariousconfigurationqualifiersprovidedbythelatestAndroidSDK.

http://developer.android.com/guide/practices/screens_support.htmlGuidelinesonhowtodesignAndroidapplicationsformultiplescreensizes.

http://developer.android.com/reference/android/content/res/Resources.htmlVariousJavamethodsavailabletoreadresources.

http://developer.android.com/reference/android/R.htmlResourcesasdefinedtothecoreAndroidplatform.

http://androidbook.com/item/3542:Ourresearchonplurals,stringarrays,resourcequalifiers,andalternateresources,aswellaslinkstootherreferences.

http://androidbook.com/item/4236:Usingdrawableresourcestocontrolbackgrounds.

http://developer.android.com/training/notepad/index.htmlAbeginner’sguide,yetacomprehensiveintroductiontoAndroidapplicationsthroughaNotePadexample.

http://developer.android.com/reference/android/content/Intent.htmlOverviewofintents,includingwell-knownactions,extras,andsoon.

http://developer.android.com/guide/appendix/g-app-intents.html:ListstheintentsforasetofGoogleapplications.Here,youwillseeherehowtoinvokeBrowser,Map,Dialer,andGoogleStreetView.

http://developer.android.com/reference/android/content/IntentFilter.htmlTalksaboutintentfiltersandisusefulwhenyouareregisteringintentfiltersforactivitiesandothercomponentsinthemanifestfile.

http://developer.android.com/guide/topics/intents/intents-filters.html:Goesintotheresolutionrulesofintentfilters.

http://developer.android.com/training/notepad/index.html

URLwhereyoucandownloadthesamplecodeforaNotePadapplication.ThisisagoodsampleapplicationthatfeaturesanumberofAndroidAPIs.Agoodplacetogoafterthecalculatorapplication.

http://developer.android.com/samples/index.html:ThisistheprimarylinktobrowsethroughthevarioussamplespresentedfortheAndroidSDKbyGoogle.

http://developer.android.com/training/index.html:ThisistheprimarylearningsitefromGooglethatpresentsaseriesoflessonstolearnAndroid.

https://code.google.com/p/openintents/:AwebefforttomakevariousAndroidapplicationsworktogether.

http://androidbook.com/item/4623:AroadmapforlearningAndroid.Althoughsomeofthesepointsarecoveredhere,seethisURLforthelatestguidanceonlearningandmaximizingAndroid.

http://androidbook.com/item/4764:ThisisaknowledgefoldercontainingaseriesofarticlesandtidbitsonprogrammingwithAndroidbasicUI.

http://www.androidbook.com/proandroid5/projects.Lookhereforalistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch02_Calculator.zip.

SummaryThischapterlaidouteverythingyouneedtounderstandtocreatemobileapplicationswiththeAndroidSDK.YouhaveseenhowUIisconstructed.Youknowwhatactivitiesare.Youknowtheintricaciesoftheactivitylifecycle.Youunderstoodresourcesandintents.Youknowhowtosavestate.Finally,yougottoseethebreadthoftheAndroidSDKbyreadingthelearningtracksthatsummarizedtherestofthebook.WehopethesefirsttwochaptersgaveyouaheadstartforyourdevelopmenteffortswiththeAndroidSDK.

Chapter3

BuildingBasicUserInterfacesandUsingControlsThepreviouschaptergaveyouacrashcourseinsomeoftheUIelementsavailableinAndroid,andhowtoputthemtogetherquicklytocreatethecalculatorapplication.Whilethatwasfun,wehopeyoustartedthinkingaboutwhatotherUIwidgetsareavailableinAndroid,overandabovetheTextView,EditText,andButtoncontrolsintroducedinChapter2.

Inthischapter,wearegoingtodiscussuserinterfacesandcontrolsindetail.WewillbeginbydiscussingthegeneralphilosophyofUIdevelopmentinAndroid,andthenwe’lldescribemanyoftheUIcontrolsthatshipwiththeAndroidSDK.Thesearethebuildingblocksoftheinterfacesyou’llcreate.Inthesubsequentchapters,wewillalsodiscussviewadaptersandlayoutmanagers,andyou’llseehowtheybuildonthebasiccontrolsweintroduceinthischapter.

Bytheendofthischapter,you’llhaveasolidunderstandingofthemanyUIcontrolsavailableinthestockAndroidtoolset,andhowtolayoutUIcontrolsintoscreensandpopulatethemwithdata.

UIDevelopmentinAndroidUIdevelopmentinAndroidisfun.It’sfunbecauseit’srelativelyeasy.WithAndroid,wehaveasimple-to-understandframeworkwithalimitedsetofout-of-the-boxcontrols.Theavailablescreenareaisgenerallylimited—onphonesifnotontablets—andthisguidestheunderlyingphilosophyof“simplepower”forAndroidcontrols.AndroidalsotakescareofalotoftheheavyliftingnormallyassociatedtodesigningandbuildingqualityUIs.This,combinedwiththefactthattheuserusuallywantstodoonespecificaction,allowsustoeasilybuildagoodUItodeliveragooduserexperience.

TheAndroidSDKshipswithahostofcontrolsthatyoucanusetobuildUIsforyourapplication.SimilartootherSDKs,theAndroidSDKprovidestextfields,buttons,lists,grids,andsoon.Inaddition,Androidprovidesacollectionofcontrolsthatareappropriateformobiledevices.

Attheheartofthecommoncontrolsaretwoclasses:android.view.Viewandandroid.view.ViewGroup.Asthenameofthefirstclasssuggests,theViewclassrepresentsageneral-purposeViewobject.ThecommoncontrolsinAndroidultimatelyextendtheViewclass.ViewGroupisalsoaview,butitcontainsotherviewstoo.ViewGroupisthebaseclassforalistoflayoutclasses.Android,likeSwing,usestheconceptoflayoutstomanagehowcontrolsarelaidoutwithinacontainerview.Usinglayouts,aswe’llsee,makesiteasyforustocontrolthepositionandorientationofthe

controlsinourUIs.

YoucanchoosefromseveralapproachestobuildUIsinAndroid.YoucanconstructUIsentirelyincode.YoucanalsodefineUIsinXML.Youcanevencombinethetwo—definetheUIinXMLandthenrefertoit,andmodifyit,incode.Todemonstratethis,inthischapterwearegoingtobuildasimpleUIusingeachofthesethreeapproaches.

Beforewegetstarted,let’sdefinesomenomenclature.InthisbookandotherAndroidliterature,youwillfindthetermsview,control,widget,container,andlayoutindiscussionsregardingUIdevelopment.IfyouarenewtoAndroidprogrammingorUIdevelopmentingeneral,youmightnotbefamiliarwiththeseterms.We’llbrieflydescribethembeforewegetstarted(seeTable3-1).

Table3-1.UINomenclature

Term Description

View,widget,control

EachoftheserepresentsaUIelement.Examplesincludeabutton,agrid,alist,awindow,adialogbox,andsoon.Thetermsview,widget,andcontrolareusedinterchangeablyinthischapter.

Container Thisisaviewusedtocontainotherviews.Forexample,agridcanbeconsideredacontainerbecauseitcontainscells,eachofwhichisaview.

Layout Thisisavisualarrangementofcontainersandviewsandcanincludeotherlayouts.WewillworkwithlayoutsinthischapterandreturnforafullexplorationofAndroid’sLayoutfeaturesinChapter5.

Figure3-1showsascreenshotoftheapplicationthatwearegoingtobuild.Nexttothescreenshotisthelayouthierarchyofthecontrolsandcontainersintheapplication.

Figure3-1.TheUIandlayoutofanactivity

Wewillrefertothislayouthierarchyaswediscussthesampleprograms.Fornow,knowthattheapplicationhasoneactivity.TheUIfortheactivityiscomposedofthreecontainers:acontainerthatcontainsaperson’sname,acontainerthatcontainstheaddress,andanouterparentcontainerforthechildcontainers.

BuildingaUICompletelyinCodeThefirstexample,Listing3-1,demonstrateshowtobuildtheUIentirelyincode.Totrythis,createanewAndroidApplicationprojectusingaprojectnameofcontrols,apackagenameofcom.androidbook.controls,andwithanactivitynamed

MainActivityandthencopythecodefromListing3-1intoyourMainActivityclass.

NoteWewillgiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoEclipsedirectlyinsteadofcopyingandpastingcode.

Listing3-1.CreatingaSimpleUserInterfaceEntirelyinCode

packagecom.androidbook.controls;importandroid.app.Activity;importandroid.os.Bundle;importandroid.view.ViewGroup.LayoutParams;importandroid.widget.LinearLayout;importandroid.widget.TextView;publicclassMainActivityextendsActivity{privateLinearLayoutnameContainer;

privateLinearLayoutaddressContainer;

privateLinearLayoutparentContainer;

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);

createNameContainer();

createAddressContainer();

createParentContainer();

setContentView(parentContainer);}

privatevoidcreateNameContainer(){nameContainer=newLinearLayout(this);

nameContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));nameContainer.setOrientation(LinearLayout.HORIZONTAL);

TextViewnameLbl=newTextView(this);

nameLbl.setText("Name:");

TextViewnameValue=newTextView(this);nameValue.setText("JohnDoe");

nameContainer.addView(nameLbl);nameContainer.addView(nameValue);}

privatevoidcreateAddressContainer(){addressContainer=newLinearLayout(this);

addressContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));addressContainer.setOrientation(LinearLayout.VERTICAL);

TextViewaddrLbl=newTextView(this);addrLbl.setText("Address:");

TextViewaddrValue=newTextView(this);addrValue.setText("911HollywoodBlvd");

addressContainer.addView(addrLbl);addressContainer.addView(addrValue);}

privatevoidcreateParentContainer(){parentContainer=newLinearLayout(this);

parentContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));parentContainer.setOrientation(LinearLayout.VERTICAL);

parentContainer.addView(nameContainer);parentContainer.addView(addressContainer);}}

AsshowninListing3-1,theactivitycontainsthreeLinearLayoutobjects.WewillbediscussinglayoutsinmuchmoredepthinChapter5,butthere’salittlechicken-and-eggissueofneedingtoknowjustalittleaboutlayoutssoonecanlearnaboutthemanybasiccontrols.Fornow,it’senoughtoknowthatlayoutobjectscontainlogictopositionobjectswithinaportionofthescreen.ALinearLayout,forexample,knowshowtolayoutcontrolseitherverticallyorhorizontally.Layoutobjectscancontainanytypeofview—

evenotherlayouts.

ThenameContainerobjectcontainstwoTextViewcontrols:oneforthelabelName:andtheothertoholdtheactualtextforthename(suchasJohnDoe).TheaddressContaineralsocontainstwoTextViewcontrols.ThedifferencebetweenthetwocontainersisthatthenameContainerislaidouthorizontallyandtheaddressContainerislaidoutvertically.BothofthesecontainerslivewithintheparentContainer,whichistherootviewoftheactivity.Afterthecontainershavebeenbuilt,theactivitysetsthecontentoftheviewtotherootviewbycallingsetContentView(parentContainer).WhenitcomestimetorendertheUIoftheactivity,therootviewiscalledtorenderitself.Therootviewthencallsitschildrentorenderthemselves,andthechildcontrolscalltheirchildren,andsoon,untiltheentireUIisrendered.

AsshowninListing3-1,wehaveseveralLinearLayoutcontrols.Twoofthemarelaidoutvertically,andoneislaidouthorizontally.ThenameContainerislaidouthorizontally.ThismeansthetwoTextViewcontrolsappearsidebysidehorizontally.TheaddressContainerislaidoutvertically,whichmeansthetwoTextViewcontrolsarestackedoneontopoftheother.TheparentContainerisalsolaidoutvertically,whichiswhythenameContainerappearsabovetheaddressContainer.Noteasubtledifferencebetweenthetwoverticallylaid-outcontainers,addressContainerandparentContainer.parentContainerissettotakeuptheentirewidthandheightofthescreen:

parentContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));

andaddressContainerwrapsitscontentvertically:

addressContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));

Saidanotherway,WRAP_CONTENTmeanstheviewshouldtakejustthespaceitneedsinthatdimensionandnomore,uptowhatthecontainingviewwillallow.FortheaddressContainer,thismeansthecontainerwilltaketwolinesvertically,becausethat’sallitneedsforthedummyaddresswehaveprovided.

BuildingaUICompletelyinXMLNowlet’sbuildthesameUIinXML(seeListing3-2).XMLlayoutfilesarestoredundertheresources(/res/)directoryinafoldercalledlayout.Totrythisexample,createanewAndroidprojectinEclipse.Bydefault,youwillgetanXMLlayoutfilenamedactivity_main.xml,locatedundertheres/layoutfolder.Double-clickactivity_main.xmltoseethecontents.Eclipsewilldisplayavisualeditorforyour

layoutfile.Youprobablyhaveastringatthetopoftheviewthatsays“HelloWorld,MainActivity!”orsomethinglikethat.Clicktheactivity_main.xmltabatthebottomoftheviewtoseetheXMLoftheactivity_main.xmlfile.ThisrevealsaLinearLayoutandaTextViewcontrol.UsingeithertheLayoutoractivity_main.xmltab,orboth,re-createListing3-2intheactivity_main.xmlfile.Saveit.

Listing3-2.CreatingaUserInterfaceEntirelyinXML

<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><!--NAMECONTAINER--><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Name:"/>

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="JohnDoe"/>

</LinearLayout>

<!--ADDRESSCONTAINER--><LinearLayoutandroid:orientation="vertical"android:layout_width="fill_parent"android:layout_height="wrap_content">

<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Address:"/>

<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="911HollywoodBlvd"/></LinearLayout>

</LinearLayout>

Underyournewproject’ssrcdirectory,thereisa.javafilecontaininganActivityclassdefinition.Double-clickthatfiletoseeitscontents.Noticethestatement

setContentView(R.layout.activity_main).TheXMLsnippetshowninListing3-2,combinedwithacalltosetContentView(R.layout.activity_main),willrenderthesameUIasbeforewhenwegenerateditcompletelyincode.TheXMLfileisself-explanatory,butnotethatwehavethreecontainerviewsdefined.ThefirstLinearLayoutistheequivalentofourparentcontainer.Thiscontainersetsitsorientationtoverticalbysettingthecorrespondingpropertylikethis:android:orientation=“vertical”.TheparentcontainercontainstwoLinearLayoutcontainers,whichrepresentnameContainerandaddressContainer.

RunningthisapplicationwillproducethesameUIasourpreviousexampleapplication.ThelabelsandvalueswillbedisplayedasshowninFigure3-1.

BuildingaUIinXMLwithCodeListing3-2isacontrivedexample.Itdoesn’tmakeanysensetohard-codethevaluesoftheTextViewcontrolsintheXMLlayout.Ideally,weshoulddesignourUIsinXMLandthenreferencethecontrolsfromcode.Thisapproachenablesustobinddynamicdatatothecontrolsdefinedatdesigntime.Infact,thisistherecommendedapproach.ItisfairlyeasytobuildlayoutsinXMLandthenusecodetopopulatethedynamicdata.

Listing3-3showsthesameUIwithslightlydifferentXML.ThisXMLassignsIDstotheTextViewcontrolssothatwecanrefertothemincode.

Listing3-3.CreatingaUserInterfaceinXMLwithIDs

<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><!--NAMECONTAINER--><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/name_text"/>

<TextViewandroid:id="@+id/nameValue"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

</LinearLayout>

<!--ADDRESSCONTAINER-->

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="wrap_content">

<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="@string/addr_text"/>

<TextViewandroid:id="@+id/addrValue"android:layout_width="fill_parent"android:layout_height="wrap_content"/></LinearLayout>

</LinearLayout>

InadditiontoaddingtheIDstotheTextViewcontrolsthatwewanttopopulatefromcode,wealsohavelabelTextViewcontrolsthatwe’repopulatingwithtextfromourstringsresourcefile.ThesearetheTextViewswithoutIDsthathaveanandroid:textattribute.TheactualstringsfortheseTextViewswillcomefromourstrings.xmlfileinthe/res/valuesfolder.Listing3-4showswhatourstrings.xmlfilemightlooklike.

Listing3-4.strings.xmlFileforListing3-3

<?xmlversion="1.0"encoding="utf-8"?><resources><stringname="app_name">CommonControls</string><stringname="name_text">Name:</string><stringname="addr_text">Address:</string></resources>

ThecodeinListing3-5demonstrateshowyoucanobtainreferencestothecontrolsdefinedintheXMLtosettheirproperties.YoumightputthisintoyouronCreate()methodforyouractivity.

Listing3-5.ReferringtoControlsinResourcesatRuntime

setContentView(R.layout.activity_main);TextViewnameValue=(TextView)findViewById(R.id.nameValue);nameValue.setText("JohnDoe");TextViewaddrValue=(TextView)findViewById(R.id.addrValue);addrValue.setText("911HollywoodBlvd.");

ThecodeinListing3-5isstraightforward,butnotethatweloadtheresourcebycallingsetContentView(R.layout.activity_main)beforecallingfindViewById()—wecannotgetreferencestoviewsiftheyhavenotbeenloadedyet.

ThedevelopersofAndroidhavedoneanicejobofmakingjustabouteveryaspectofacontrolsettableviaXMLorcode.It’susuallyagoodideatosetthecontrol’sattributesintheXMLlayoutfileratherthanusingcode.However,therewillbeavarietyoftimeswhenyouneedtousecode,suchassettingavaluetobedisplayedtotheuser.

FILL_PARENTvs.MATCH_PARENTTheconstantFILL_PARENTwasdeprecatedinAndroid2.2andreplacedwithMATCH_PARENT.Thiswasstrictlyanamechange,though.Thevalueofthisconstantisstill–1.Similarly,forXMLlayouts,fill_parentwasreplacedwithmatch_parent.Sowhatvaluedoyouuse?InsteadofFILL_PARENTorMATCH_PARENT,youcouldsimplyusethevalue–1,andyou’dbefine.However,thisisn’tveryeasytoread,andyoudon’thaveanequivalentunnamedvaluetousewithyourXMLlayouts.There’sabetterway.

DependingonwhichAndroidAPIsyouneedtouseinyourapplication,youcaneitherbuildyourapplicationagainstaversionofAndroidbefore2.2andrelyonforwardcompatibilityorbuildyourapplicationagainstversion2.2orlaterofAndroidandsetminSdkVersiontothelowestversionofAndroidyourapplicationwillrunon.Forexample,ifyouonlyneedAPIsthatexistedinAndroid1.6,buildagainstAndroid1.6anduseFILL_PARENTandfill_parent.YourapplicationshouldrunwithnoproblemsinalllaterversionsofAndroidincluding2.2andbeyond.IfyouneedAPIsfromAndroid2.2orlater,goaheadandbuildagainstthatversionofAndroid,useMATCH_PARENTandmatch_parent,andsetminSdkVersiontosomethingolder:forexample,4(forAndroid1.6).YoucanstilldeployanAndroidapplicationbuiltinAndroid2.2toanolderversionofAndroid,butyou’llhavetobecarefulabouttheclassesand/ormethodsthataren’tintheearlierreleasesoftheAndroidSDK.Therearewaysaroundthis,suchasusingreflectionorcreatingwrapperclassestohandledifferencesinAndroidversions.Wewillcoverthoseadvancedtopicsinlaterchapters.

UnderstandingAndroid’sCommonControlsWewillnowstartourdiscussionofthecommoncontrolsintheAndroidSDK.We’llstartwithtextcontrolsandthencoverbuttons,checkboxes,radiobuttons,lists,grids,dateandtimecontrols,andamap-viewcontrol.Thesewillgohandinhandwiththelayoutcontrolswe’llintroduceinChapter4.

TextControlsTextcontrolsarelikelytobethefirsttypeofcontrolthatyou’llworkwithinAndroid.Androidhasacompletebutnotoverwhelmingsetoftextcontrols.Inthissection,wearegoingtodiscusstheTextView,EditText,AutoCompleteTextView,andMultiAutoCompleteTextViewcontrols.Figure3-2showsthecontrolsinaction.

Figure3-2.TextcontrolsinAndroid

TextViewYou’vealreadyseenasimpleXMLspecificationforaTextViewcontrol,inListing3-3,andhowtohandleTextViewsincodeinListing3-4.NoticehowwespecifiedtheID,width,height,andvalueofthetextinXMLandhowwesetthevalueusingthesetText()method.TheTextViewcontrolknowshowtodisplaytextbutdoesnotallowediting.Thismightleadyoutoconcludethatthecontrolisessentiallyadummylabel.Nottrue.TheTextViewcontrolhasafewinterestingpropertiesthatmakeitveryhandy.IfyouknowthatthecontentoftheTextViewisgoingtocontainawebURLorane-mailaddress,forexample,youcansettheautoLinkpropertytoemail|web,andthecontrolwillfindandhighlightanye-mailaddressesandURLs.Moreover,whentheuserclicksoneofthesehighlighteditems,thesystemwilltakecareoflaunchingthee-mailapplicationwiththee-mailaddressorabrowserwiththeURL.InXML,thisattributewouldbeinsidetheTextViewtagandwouldlooksomethinglikethis:

<TextView...android:autoLink="email|web".../>

Youspecifyapipe-delimitedsetofvaluesincludingweb,email,phone,ormap,oruse

none(thedefault)orall.IfyouwanttosetautoLinkbehaviorincodeinsteadofusingXML,thecorrespondingmethodcallissetAutoLinkMask().Youwouldpassitanintrepresentingthecombinationofvaluessortoflikebefore,suchasLinkify.EMAIL_ADDRESSES|Linkify.WEB_URLS.Toachievethisfunctionality,TextViewisutilizingtheandroid.text.util.Linkifyclass.Listing3-6showsanexampleofauto-linkingwithcode.

Listing3-6.UsingLinkifyonTextinaTextView

TextViewtv=(TextView)this.findViewById(R.id.tv);tv.setAutoLinkMask(Linkify.ALL);tv.setText("Pleasevisitmywebsite,http://www.androidbook.comoremailmeatdavemac327@gmail.com.");

Noticethatwesettheauto-linkoptionsonourTextViewbeforewesetthetext.Thisisimportantbecausesettingtheauto-linkoptionsaftersettingthetextwon’taffecttheexistingtext.Becausewe’reusingcodetoaddhyperlinkstoourtext,ourXMLfortheTextViewinListing3-6doesnotrequireanyspecialattributesandcanlookassimpleasthis:

<TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

Ifyouwantto,youcaninvokethestaticaddLinks()methodoftheLinkifyclasstofindandaddlinkstothecontentofanyTextVieworanySpannableondemand.InsteadofusingsetAutoLinkMask(),wecouldhavedonethefollowingaftersettingthetext:

Linkify.addLinks(tv,Linkify.ALL);

Clickingalinkwillcausethedefaultintenttobecalledforthataction.Forexample,clickingawebURLwilllaunchthebrowserwiththeURL.Clickingaphonenumberwilllaunchthephonedialer,andsoon.TheLinkifyclasscanperformthisworkrightoutofthebox.

Linkifycanalsodetectcustompatternsyouwanttolookfor,decidewhethertheyareamatchforsomethingyoudecideneedstobeclickable,andsetuphowtofireanintenttomakeaclickturnintosomesortofaction.Wewon’tgointothosedetailshere,butknowthatthesethingscanbedone.

TherearemanymorefeaturesofTextViewtoexplore,fromfontattributestominLinesandmaxLinesandmanymore.Thesearefairlyself-explanatory,andyouareencouragedtoexperimenttoseehowyoumightbeabletousethem.AlthoughyoushouldkeepinmindthatsomefunctionalityintheTextViewclassisnotapplicabletoaread-onlyfield,thefunctionalityisthereforthesubclassesofTextView,oneofwhichwewillcovernext.

EditTextTheEditTextcontrolisasubclassofTextView.Assuggestedbythename,theEditTextcontrolallowsfortextediting.EditTextisnotaspowerfulasthetext-editingcontrolsthatyoufindontheInternet,butusersofAndroid-baseddevicesprobablywon’ttypedocumentsdirectlyintoanEditTextcontrol—they’lltypeacoupleofparagraphsatmostoruseamorefullyfunctionalHTML-basedpageinstead.Therefore,theclasshaslimitedbutappropriatefunctionalityandmayevensurpriseyou.Forexample,oneofthemostsignificantpropertiesofanEditTextistheinputType.YoucansettheinputTypepropertytotextAutoCorrecttohavethecontrolcorrectcommonmisspellings.YoucansetittotextCapWordstohavethecontrolcapitalizewords.Otheroptionsexpectonlyphonenumbersorpasswords.

Thereareolder,nowdeprecated,waysofspecifyingcapitalization,multilinetext,andotherfeatures.IfthesearespecifiedwithoutaninputTypeproperty,theycanberead;butifinputTypeisspecified,theseolderpropertiesareignored.

TheolddefaultbehavioroftheEditTextcontrolistodisplaytextononelineandexpandasneeded.Inotherwords,iftheusertypespastthefirstline,anotherlinewillappear,andsoon.Youcould,however,forcetheusertoasinglelinebysettingthesingleLinepropertytotrue.Inthiscase,theuserwillhavetocontinuetypingonthesameline.WithinputType,ifyoudon’tspecifytextMultiLine,theEditTextwilldefaulttosingle-lineonly.Soifyouwanttheolddefaultbehaviorofmultilinetyping,youneedtospecifyinputTypewithtextMultiLine.

OneofthenicefeaturesofEditTextisthatyoucanspecifyhinttext.Thistextwillbedisplayedslightlyfadedanddisappearsassoonastheuserstartstotypetext.Thepurposeofthehintistolettheuserknowwhatisexpectedinthisfield,withouttheuserhavingtoselectanderasedefaulttext.InXML,thisattributeisandroid:hint=“yourhinttexthere”orandroid:hint=”@string/your_hint_name”,whereyour_hint_nameisaresourcenameofastringtobefoundin/res/values/strings.xml.Incode,youwouldcallthesetHint()methodwitheitheraCharSequenceoraresourceID.

AutoCompleteTextViewTheAutoCompleteTextViewcontrolisaTextViewwithauto-completefunctionality.Inotherwords,astheusertypesintheTextView,thecontrolcandisplaysuggestionsforselection.Listing3-7demonstratestheAutoCompleteTextViewcontrolwithXMLandwiththecorrespondingcode.

Listing3-7.UsinganAutoCompleteTextViewControl

<AutoCompleteTextViewandroid:id="@+id/actv"android:layout_width="fill_parent"android:layout_height="wrap_content"/>AutoCompleteTextViewactv=(AutoCompleteTextView)

this.findViewById(R.id.actv);

ArrayAdapter<String>aa=newArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line,newString[]{"English","Hebrew","Hindi","Spanish","German","Greek"});

actv.setAdapter(aa);

TheAutoCompleteTextViewcontrolshowninListing3-7suggestsalanguagetotheuser.Forexample,iftheusertypesen,thecontrolsuggestsEnglish.Iftheusertypesgr,thecontrolrecommendsGreek,andsoon.

Ifyouhaveusedasuggestioncontrolorasimilarauto-completecontrol,youknowthatcontrolslikethishavetwoparts:atext-viewcontrolandacontrolthatdisplaysthesuggestion(s).That’sthegeneralconcept.Touseacontrollikethis,youhavetocreatethecontrol,createthelistofsuggestions,tellthecontrolthelistofsuggestions,andpossiblytellthecontrolhowtodisplaythesuggestions.Alternatively,youcouldcreateasecondcontrolforthesuggestionsandthenassociatethetwocontrols.

Androidhasmadethissimple,asisevidentfromListing3-7.TouseanAutoCompleteTextView,youcandefinethecontrolinyourlayoutfileandreferenceitinyouractivity.YouthencreateanadapterclassthatholdsthesuggestionsanddefinetheIDofthecontrolthatwillshowthesuggestion(inthiscase,asimplelistitem).InListing3-7,thesecondparametertotheArrayAdaptertellstheadaptertouseasimplelistitemtoshowthesuggestion.ThefinalstepistoassociatetheadapterwiththeAutoCompleteTextView,whichyoudousingthesetAdapter()method.Don’tworryabouttheadapterforthemoment;we’llcoverthoselaterinthischapter.

MultiAutoCompleteTextViewIfyouhaveplayedwiththeAutoCompleteTextViewcontrol,youknowthatthecontrolofferssuggestionsonlyfortheentiretextinthetextview.Inotherwords,ifyoutypeasentence,youdon’tgetsuggestionsforeachword.That’swhereMultiAutoCompleteTextViewcomesin.YoucanusetheMultiAutoCompleteTextViewtoprovidesuggestionsastheusertypes.Forexample,Figure3-2showsthattheusertypedthewordEnglishfollowedbyacomma,andthenGe,atwhichpointthecontrolsuggestedGerman.Iftheuserweretocontinue,thecontrolwouldofferadditionalsuggestions.

UsingtheMultiAutoCompleteTextViewislikeusingtheAutoCompleteTextView.Thedifferenceisthatyouhavetotellthecontrolwheretostartsuggestingagain.Forexample,inFigure3-2,youcanseethatthecontrolcanoffersuggestionsatthebeginningofthesentenceandafteritseesacomma.TheMultiAutoCompleteTextViewcontrolrequiresthatyougiveitatokenizerthatcanparsethesentenceandtellitwhethertostartsuggestingagain.Listing3-8demonstratesusingtheMultiAutoCompleteTextViewcontrolwiththeXMLandthentheJava

code.

Listing3-8.UsingtheMultiAutoCompleteTextViewControl

<MultiAutoCompleteTextViewandroid:id="@+id/mactv"android:layout_width="fill_parent"android:layout_height="wrap_content"/>

MultiAutoCompleteTextViewmactv=(MultiAutoCompleteTextView)this.findViewById(R.id.mactv);ArrayAdapter<String>aa2=newArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line,newString[]{"English","Hebrew","Hindi","Spanish","German","Greek"});

mactv.setAdapter(aa2);

mactv.setTokenizer(newMultiAutoCompleteTextView.CommaTokenizer());

TheonlysignificantdifferencesbetweenListings3-7and3-8aretheuseofMultiAutoCompleteTextViewandthecalltothesetTokenizer()method.BecauseoftheCommaTokenizerinthiscase,afteracommaistypedintotheEditTextfield,thefieldwillagainmakesuggestionsusingthearrayofstrings.Anyothercharacterstypedinwillnottriggerthefieldtomakesuggestions.SoevenifyouweretotypeFrenchSpani,thepartialwordSpaniwouldnottriggerthesuggestionbecauseitdidnotfollowacomma.Androidprovidesanothertokenizerfore-mailaddressescalledRfc822Tokenizer.Youcanalwayscreateyourowntokenizerifyouwantto.

ButtonControlsButtonsarecommoninanywidgettoolkit,andAndroidisnoexception.Androidoffersthetypicalsetofbuttonsaswellasafewextras.Inthissection,wewilldiscussthreetypesofbuttoncontrols:thebasicbutton,theimagebutton,andthetogglebutton.Figure3-3showsaUIwiththesecontrols.Thebuttonatthetopisthebasicbutton,themiddlebuttonisanimagebutton,andthelastoneisatogglebutton.

Figure3-3.Androidbuttoncontrols

Let’sgetstartedwiththebasicbutton.

TheButtonControlThebasicbuttonclassinAndroidisandroid.widget.Button.There’snotmuchtothistypeofbutton,beyondhowyouuseittohandleclickevents.Listing3-9showsafragmentofanXMLlayoutfortheButtoncontrol,plussomeJavathatwemightsetupintheonCreate()methodofouractivity.OurbasicbuttonwouldlooklikethetopbuttoninFigure3-3.

Listing3-9.HandlingClickEventsonaButton

<Buttonandroid:id="@+id/button1"android:text="@string/basicBtnLabel"android:layout_width="fill_parent"android:layout_height="wrap_content"/>

Buttonbutton1=(Button)this.findViewById(R.id.button1);button1.setOnClickListener(newOnClickListener(){publicvoidonClick(Viewv){Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse("http://www.androidbook.comstartActivity(intent);}});

Listing3-9showshowtoregisterforabutton-clickevent.Youregisterfortheon-clickeventbycallingthesetOnClickListener()methodwithanOnClickListener.InListing3-9,ananonymouslisteneriscreatedontheflytohandleclickeventsfor

button1.Whenthebuttonisclicked,theonClick()methodofthelisteneriscalledand,inthiscase,launchesthebrowsertoourwebsite.

SinceAndroidSDK1.6,thereisaneasierwaytosetupaclickhandlerforyourbuttonorbuttons.Listing3-10showstheXMLforaButtonwhereyouspecifyanattributeforthehandler,plustheJavacodethatistheclickhandler.

Listing3-10.SettingUpaClickHandlerforaButton

<Button...android:onClick="myClickHandler".../>publicvoidmyClickHandler(Viewtarget){switch(target.getId()){caseR.id.button1:...

ThehandlermethodwillbecalledwithtargetsettotheViewobjectrepresentingthebuttonthatwasclicked.NoticehowtheswitchstatementintheclickhandlermethodusestheresourceIDsofthebuttonstoselectthelogictorun.Usingthismethodmeansyouwon’thavetoexplicitlycreateeachButtonobjectinyourcode,andyoucanreusethesamemethodacrossmultiplebuttons.Thismakesthingseasiertounderstandandmaintain.Thisworkswiththeotherbuttontypesaswell.

TheImageButtonControlAndroidprovidesanimagebuttonviaandroid.widget.ImageButton.Usinganimagebuttonissimilartousingthebasicbutton(seeListing3-11).OurimagebuttonwouldlooklikethemiddlebuttoninFigure3-3.

Listing3-11.UsinganImageButton

<ImageButtonandroid:id="@+id/imageButton2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="myClickHandler"android:src="@drawable/icon"/>

ImageButtonimageButton2=(ImageButton)this.findViewById(R.id.imageButton2);imageButton2.setImageResource(R.drawable.icon);

Herewe’vecreatedtheimagebuttoninXMLandsetthebutton’simagefromadrawableresource.Theimagefileforthebuttonmustexistunder/res/drawable.Inourcase,we’resimplyreusingtheAndroidiconforthebutton.WealsoshowinListing3-11howyoucansetthebutton’simagedynamicallybycallingsetImageResource()methodonthebuttonandpassingitaresourceID.Notethatyouonlyneedtodooneortheother.Youdon’tneedtospecifythebuttonimagebothintheXMLfileandincode.

Oneofthenicefeaturesofanimagebuttonisthatyoucanspecifyatransparentbackgroundforthebutton.Theresultwillbeaclickableimagethatactslikeabuttonbut

canlooklikewhateveryouwantittolooklike.Justsetandroid:background=”@null”fortheimagebutton.

Becauseyourimagemaybesomethingverydifferentthanastandardbutton,youcancustomizehowthebuttonlooksinthetwootherstatesitcanbeinwhenusedinyourUI.Besidesappearingasnormal,buttonscanhavefocus,andtheycanbepressed.Havingfocussimplymeansthebuttoniscurrentlywhereeventswillgo.YoucandirectfocustoabuttonusingthearrowkeysonthekeypadorD-pad,forexample.Pressedmeansthatthebutton’sappearancechangeswhenithasbeenpressedbutbeforetheuserhasletgo.TotellAndroidwhatthethreeimagesareforourbutton,andwhichoneiswhich,wesetupaselector.ThisisasimpleXMLfile,imagebuttonselector,thatresidesinthe/res/drawablefolderofourproject.Thisissomewhatcounterintuitive,becausethisisanXMLfileandnotanimagefile,yetthatiswheretheselectorfilemustgo.ThecontentofaselectorfilewilllooklikeListing3-12.

Listing3-12.UsingaSelectorwithanImageButton

<?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:state_pressed="true"android:drawable="@drawable/button_pressed"/><!--pressed--><itemandroid:state_focused="true"android:drawable="@drawable/button_focused"/><!--focused--><itemandroid:drawable="@drawable/icon"/><!--default--></selector>

Thereareseveralthingstonoteabouttheselectorfile.First,youdonotspecifya<resources>tagasinvaluesXMLfiles.Second,theorderofthebuttonimagesisimportant.Androidwilltesteachitemintheselector,inorder,toseeifitmatches.Therefore,youwantthenormalimagetobelastsoitisusedonlyifthebuttonisnotpressedandifthebuttondoesnothavefocus.Ifthenormalimagewaslistedfirst,itwouldalwaysmatchandbeselectedevenifthebuttonispressedorhasfocus.Ofcourse,thedrawablesyourefertomustexistinthe/res/drawablesfolder.InthedefinitionofyourbuttoninthelayoutXMLfile,youwanttosettheandroid:srcpropertytotheselectorXMLfileasifitwerearegulardrawable,likeso:

<Button...android:src="@drawable/imagebuttonselector".../>

TheToggleButtonControlTheToggleButtoncontrol,likeacheckboxoraradiobutton,isatwo-statebutton.ThisbuttoncanbeineithertheOnorOffstate.AsshowninFigure3-3,the

ToggleButton’sdefaultbehavioristoshowacoloredbarwhenintheOnstateandagrayed-outbarwhenintheOffstate.Moreover,thedefaultbehavioralsosetsthebutton’stexttoOnwhenit’sintheOnstateandOffwhenit’sintheOffstate.YoucanmodifythetextfortheToggleButtonifOn/Offisnotappropriateforyourapplication.Forexample,ifyouhaveabackgroundprocessthatyouwanttostartandstopviaaToggleButton,youcouldsetthebutton’stexttoStopandRunbyusingandroid:textOnandandroid:textOffproperties.

Listing3-13showsanexample.OurtogglebuttonisthebottombuttoninFigure3-3,anditisintheOnposition,sothelabelonthebuttonsaysStop.

Listing3-13.TheAndroidToggleButton

<ToggleButtonandroid:id="@+id/cctglBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="ToggleButton"android:textOn="Stop"android:textOff="Run"/>

BecauseToggleButtonshaveonandofftextasseparateattributes,theandroid:textattributeofaToggleButtonisnotreallyused.It’savailablebecauseithasbeeninherited(fromTextView),butinthiscase,youdon’tneedtouseit.

TheCheckBoxControlTheCheckBoxcontrolisanothertwo-statebuttonthatallowstheusertotoggleitsstate.Thedifferenceisthat,formanysituations,theusersdon’tviewitasabuttonthatinvokesimmediateaction.FromAndroid’spointofview,however,itisabutton,andyoucandoanythingwithacheckboxthatyoucandowithabutton.

InAndroid,youcancreateacheckboxbycreatinganinstanceofandroid.widget.CheckBox.SeeListing3-14andFigure3-4.

Listing3-14.CreatingCheckBoxes

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<CheckBoxandroid:id="@+id/chickenCB"android:text="Chicken"android:checked="true"android:layout_width=""wrap_content"android:layout_height="wrap_content"/>

<CheckBoxandroid:id="@+id/fishCB"

android:text="Fish"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<CheckBoxandroid:id="@+id/steakCB"android:text="Steak"android:checked="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

</LinearLayout>

Figure3-4.UsingtheCheckBoxcontrol

YoumanagethestateofacheckboxbycallingsetChecked()ortoggle().YoucanobtainthestatebycallingisChecked().

Ifyouneedtoimplementspecificlogicwhenacheckboxischeckedorunchecked,youcanregisterfortheon-checkedeventbycallingsetOnCheckedChangeListener()withanimplementationoftheCompoundButton.OnCheckedChangeListenerinterface.You’llthenhavetoimplementtheonCheckedChanged()method,whichwillbecalledwhenthecheckboxischeckedorunchecked.Listing3-15showsomecodethatdealswithaCheckBox.

Listing3-15.UsingCheckBoxesinCode

publicclassCheckBoxActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.checkbox);

CheckBoxfishCB=(CheckBox)findViewById(R.id.fishCB);

if(fishCB.isChecked())

fishCB.toggle();//flipsthecheckboxtouncheckedifitwaschecked

fishCB.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){

@OverridepublicvoidonCheckedChanged(CompoundButtonarg0,booleanisChecked){Log.v("CheckBoxActivity","Thefishcheckboxisnow"+(isChecked?"checked":"notchecked"));}});}}

ThenicepartofsettinguptheOnCheckedChangeListeneristhatyouarepassedthenewstateoftheCheckBoxbutton.YoucouldinsteadusetheOnClickListenertechniqueasweusedwithbasicbuttons.WhentheonClick()methodiscalled,youwouldneedtodeterminethenewstateofthebuttonbycastingitappropriatelyandthencallingisChecked()onit.Listing3-16showswhatthiscodemightlooklikeifweaddedandroid:onClick=“myClickHandler”totheXMLdefinitionofourCheckBoxbuttons.

Listing3-16.UsingCheckBoxesinCodewithandroid:onClick

publicvoidmyClickHandler(Viewview){switch(view.getId()){caseR.id.steakCB:Log.v("CheckBoxActivity","Thesteakcheckboxisnow"+(((CheckBox)view).isChecked()?"checked":"notchecked"));}}

TheSwitchControlTheSwitchwidgetwasintroducedinAndroid4.0andprovidesverysimilarbehaviortotheCheckBox.Infact,thetwowidgetsaresosimilar,you’llalmostcertainlygetasenseofdejavuwhenreviewingthecodeforaSwitchobject.Manypeople(includingsomeofthisbook’sauthors)believethattheSwitchwasintroducedforaestheticreasonsmorethananything.ThetrendinUIdesigninthelastfewyearshasbeentowardtheskewmorphicidealofwidgetslookinglikereal-worldthings,andaSwitchisaconcreteselectorintherealworld—notmanykitchenapplianceshaveaCheckBoxafterall.

TheSwitchsimilaritiestotheCheckBoxcontrolextendtocommonmethodsforexaminingandchangingstate.ThismimickingofmethodsincludessetChecked()toturntheSwitchon,isChecked()totestcurrentstate,andsoon.OneaestheticdifferenceofferedbytheSwitchwidgetistheabilitytochangetheassociatedtextbetweenstates.Additionalmethodsareavailabletocontrolthistext:

getTextOn():returnsthetextdisplayediftheSwitchison.

getTextOff():returnsthetextdisplayediftheSwitchisoff.

setTextOn():setsthetexttobedisplayediftheSwitchison.Whilegooddesignwouldusuallymeanonewouldn’tchangethetext,thereareafewcaseswherealiveupdateofsomemetricintheswitchtextcanbehelpful.

setTextOff():setsthetexttobedisplayediftheSwitchisoff.

AnexamplelayoutincludingaSwitchisshowninListing3-17.

Listing3-17.CreatingaLayoutUsingaSwitch

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<Switchandroid:id="@+id/switchdemo"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Thisswitchis:off"/>

</LinearLayout>

CautionRememberthat“switch”isareservedwordinJava.SoweuseanIDthatdoesn’tclash.

ThecodeforourexampleSwitchinListing3-18shouldprovokethosefeelingsofdejavuwementionedwithrespecttotheCheckBoxcontrol.

Listing3-18.ControllingSwitchBehaviorinCode

publicclassSwitchDemoextendsActivityimplementsCompoundButton.OnCheckedChangeListener{Switchsw;

@OverridepublicvoidonCreate(Bundleicicle){

super.onCreate(icicle);setContentView(R.layout.main);

sw=(Switch)findViewById(R.id.switchdemo);sw.setOnCheckedChangeListener(this);}

publicvoidonCheckedChanged(CompoundButtonbuttonView,booleanisChecked){if(isChecked){sw.setTextOn("Thisswitchis:on");}else{sw.setTextOff("Thisswitchis:off");}}}

TheresultsofourSwitchworkareshowinginFigure3-5.

Figure3-5.UsingtheSwitchcontrol

TheRadioButtonControlRadioButtoncontrolsareanintegralpartofanyUItoolkit.Aradiobuttongivestheusersseveralchoicesandforcesthemtoselectasingleitem.Toenforcethissingle-selectionmodel,radiobuttonsgenerallybelongtoagroup,andeachgroupisforcedtohaveonlyoneitemselectedatatime.

TocreateagroupofradiobuttonsinAndroid,firstcreateaRadioGroup,andthenpopulatethegroupwithradiobuttons.Listing3-19andFigure3-6showanexample.

Listing3-19.UsingAndroidRadioButtonWidgets

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<RadioGroupandroid:id="@+id/rBtnGrp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical">

<RadioButtonandroid:id="@+id/chRBtn"android:text="Chicken"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<RadioButtonandroid:id="@+id/fishRBtn"android:text="Fish"android:checked="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<RadioButtonandroid:id="@+id/stkRBtn"android:text="Steak"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

</RadioGroup>

</LinearLayout>

Figure3-6.Usingradiobuttons

InAndroid,youimplementaradiogroupusingandroid.widget.RadioGroupand

aradiobuttonusingandroid.widget.RadioButton.

Notethattheradiobuttonswithintheradiogroupare,bydefault,uncheckedtobeginwith,althoughyoucansetonetocheckedintheXMLdefinition,aswedidwithFishinListing3-19.Tosetoneoftheradiobuttonstothecheckedstateprogrammatically,youcanobtainareferencetotheradiobuttonandcallsetChecked():

RadioButtonsteakBtn=(RadioButton)this.findViewById(R.id.stkRBtn);steakBtn.setChecked(true);

Youcanalsousethetoggle()methodtotogglethestateoftheradiobutton.AswiththeCheckBoxcontrol,youwillbenotifiedofon-checkedoron-uncheckedeventsifyoucallthesetOnCheckedChangeListener()withanimplementationoftheOnCheckedChangeListenerinterface.Thereisaslightdifferencehere,though.Thisisadifferentclassthanbefore.Thistime,it’stechnicallytheRadioGroup.OnCheckedChangeListenerclassactingfortheRadioGroup,whereasbeforeitwastheCompoundButton.OnCheckedChangeListenerclass.

TheRadioGroupcanalsocontainviewsotherthantheradiobutton.Forexample,Listing3-20addsaTextViewafterthelastradiobutton.Alsonotethatthefirstradiobutton(anotherRadBtn)liesoutsidetheradiogroup.

Listing3-20.ARadioGroupwithMoreThanJustRadioButtons

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<RadioButtonandroid:id="@+id/anotherRadBtn"android:text="Outside"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<RadioGroupandroid:id="@+id/radGrp"android:layout_width="wrap_content"android:layout_height="wrap_content">

<RadioButtonandroid:id="@+id/chRBtn"android:text="Chicken"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<RadioButtonandroid:id="@+id/fishRBtn"android:text="Fish"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<RadioButtonandroid:id="@+id/stkRBtn"android:text="Steak"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<TextViewandroid:text="MyFavorite"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

</RadioGroup></LinearLayout>

Listing3-20showsthatyoucanhavenon-RadioButtoncontrolsinsidearadiogroup.Youshouldalsoknowthattheradiogroupcanonlyenforcesingle-selectionontheradiobuttonsinitsowncontainer.Thatis,theradiobuttonwithIDanotherRadBtnwillnotbeaffectedbytheradiogroupshowninListing3-20becauseitisnotoneofthegroup’schildren.

YoucanmanipulatetheRadioGroupprogrammatically.Forexample,youcanobtainareferencetoaradiogroupandaddaradiobutton(orothertypeofcontrol).Listing3-21demonstratesthisconcept.

Listing3-21.AddingaRadioButtontoaRadioGroupinCode

RadioGroupradGrp=(RadioGroup)findViewById(R.id.radGrp);RadioButtonnewRadioBtn=newRadioButton(this);newRadioBtn.setText("Pork");radGrp.addView(newRadioBtn);

Onceauserhascheckedaradiobuttonwithinaradiogroup,theusercannotuncheckitbyclickingitagain.TheonlywaytoclearallradiobuttonsinaradiogroupistocalltheclearCheck()methodontheRadioGroupprogrammatically.

Ofcourse,youwanttodosomethinginterestingwiththeRadioGroup.Youprobablydon’twanttopolleachRadioButtontodeterminewhetherit’schecked.Fortunately,theRadioGrouphasseveralmethodstohelpyouout.WedemonstratethosewithListing3-22.TheXMLforthiscodeisinListing3-20.

Listing3-22.UsingaRadioGroupProgrammatically

publicclassRadioGroupActivityextendsActivity{protectedstaticfinalStringTAG="RadioGroupActivity";

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.radiogroup);

RadioGroupradGrp=(RadioGroup)findViewById(R.id.radGrp);

intcheckedRadioButtonId=radGrp.getCheckedRadioButtonId();

radGrp.setOnCheckedChangeListener(newRadioGroup.OnCheckedChangeListener(){@OverridepublicvoidonCheckedChanged(RadioGrouparg0,intid){switch(id){case-1:Log.v(TAG,"Choicescleared!");break;caseR.id.chRBtn:Log.v(TAG,"ChoseChicken");break;caseR.id.fishRBtn:Log.v(TAG,"ChoseFish");break;caseR.id.stkRBtn:Log.v(TAG,"ChoseSteak");break;default:Log.v(TAG,"Huh?");break;}}});}}

WecanalwaysgetthecurrentlycheckedRadioButtonusinggetCheckedRadioButtonId(),whichreturnstheresourceIDofthecheckeditemor–1ifnothingischecked(possibleifthere’snodefaultandtheuserhasn’tchosenanoptionyet).WeshowedthisinouronCreate()methodpreviously,butinreality,you’dwanttouseitattheappropriatetimetoreadtheuser’scurrentchoice.WecanalsosetupalistenertobenotifiedimmediatelywhentheuserchoosesoneoftheRadioButtons.NoticethattheonCheckedChanged()methodtakesaRadioGroupparameter,allowingyoutousethesameOnCheckedChangeListenerformultipleRadioGroups.Youmayhavenoticedtheswitchoptionof–1.ThiscanalsooccuriftheRadioGroupisclearedthroughcodeusingclearCheck().

TheImageViewControlOneofthebasiccontrolswehaven’tcoveredyetistheImageViewcontrol.Thisisused

todisplayanimage,wheretheimagecancomefromafile,acontentprovider,oraresourcesuchasadrawable.Youcanevenspecifyjustacolor,andtheImageViewwilldisplaythatcolor.Listing3-23showssomeXMLexamplesofImageViews,followedbysomecodethatshowshowtocreateanImageView.

Listing3-23.ImageViewsinXMLandinCode

<ImageViewandroid:id="@+id/image1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/icon"/>

<ImageViewandroid:id="@+id/image2"android:layout_width="125dip"android:layout_height="25dip"android:src="#555555"/>

<ImageViewandroid:id="@+id/image3"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<ImageViewandroid:id="@+id/image4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/manatee02"android:scaleType="centerInside"android:maxWidth="35dip"android:maxHeight="50dip"/>

ImageViewimgView=(ImageView)findViewById(R.id.image3);

imgView.setImageResource(R.drawable.icon);

imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(),R.drawable.manatee14));

imgView.setImageDrawable(Drawable.createFromPath("/mnt/sdcard/dave2.jpg"));

imgView.setImageURI(Uri.parse("file://mnt/sdcard/dave2.jpg"));

Inthisexample,wehavefourimagesdefinedinXML.Thefirstissimplytheiconforourapplication.Thesecondisagraybarthatiswiderthanitistall.ThethirddefinitiondoesnotspecifyanimagesourceintheXML,butweassociateanIDwiththisone(image3)thatwecanusefromourcodetosettheimage.Thefourthimageisanotherofourdrawableimagefileswherewenotonlyspecifythesourceoftheimagefilebutalsosetthemaximumdimensionsoftheimageonthescreenanddefinewhattodoiftheimageislargerthanourmaximumsize.Inthiscase,wetelltheImageViewtocenterandscaletheimagesoitfitsinsidethesizewespecified.

IntheJavacodeofListing3-23weshowseveralwaystosettheimageofimage3.We

firstofcoursemustgetareferencetotheImageViewbyfindingitusingitsresourceID.Thefirstsettermethod,setImageResource(),simplyusestheimage’sresourceIDtolocatetheimagefiletosupplytheimageforourImageView.ThesecondsetterusestheBitmapFactorytoreadinanimageresourceintoaBitmapobjectandthensetstheImageViewtothatBitmap.NotethatwecouldhavedonesomemodificationstotheBitmapbeforeapplyingittoourImageView,butinourcase,weuseditasis.Inaddition,theBitmapFactoryhasseveralmethodsofcreatingaBitmap,includingfromabytearrayandanInputStream.YoucouldusetheInputStreammethodtoreadanimagefromawebserver,createtheBitmapimage,andthensettheImageViewfromthere.

ThethirdsettingusesaDrawableforourimagesource.Inthiscase,we’reshowingthesourceoftheimagecomingfromtheSDcard.You’llneedtoputsomesortofimagefileoutontheSDcardwiththepropernameforthistoworkforyou.SimilartoBitmapFactory,theDrawableclasshasafewdifferentwaystoconstructDrawables,includingfromanXMLstream.

ThefinalsettermethodtakestheURIofanimagefileandusesthatastheimagesource.Forthislastcall,don’tthinkthatyoucanuseanyimageURIasthesource.Thismethodisreallyonlyintendedtobeusedforlocalimagesonthedevice,notforimagesthatyoumightfindthroughHTTP.TouseInternet-basedimagesasthesourceforyourImageView,you’dmostlikelyuseBitmapFactoryandanInputStream.

DateandTimeControlsDateandtimecontrolsarecommoninmanywidgettoolkits.Androidoffersseveraldate-andtime-basedcontrols,someofwhichwe’lldiscussinthissection.Specifically,wearegoingtointroducetheDatePicker,TimePicker,DigitalClock,andAnalogClockcontrols.

TheDatePickerandTimePickerControlsAsthenamessuggest,youusetheDatePickercontroltoselectadateandtheTimePickercontroltopickatime.Listing3-24andFigure3-7showexamplesofthesecontrols.

Listing3-24.TheDatePickerandTimePickerControlsinXML

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<TextViewandroid:id="@+id/dateDefault"android:layout_width="fill_parent"android:layout_height="wrap_content"/>

<DatePickerandroid:id="@+id/datePicker"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<TextViewandroid:id="@+id/timeDefault"android:layout_width="fill_parent"android:layout_height="wrap_content"/>

<TimePickerandroid:id="@+id/timePicker"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

</LinearLayout>

Figure3-7.TheDatePickerandTimePickerUIs

IfyoulookattheXMLlayout,youcanseethatdefiningthesecontrolsiseasy.AswithanyothercontrolintheAndroidtoolkit,youcanaccessthecontrolsprogrammaticallytoinitializethemortoretrievedatafromthem.Forexample,youcaninitializethesecontrolsasshowninListing3-23.

Listing3-25.InitializingtheDatePickerandTimePickerwithDateandTime,

Respectively

publicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.datetimepicker);

TextViewdateDefault=(TextView)findViewById(R.id.dateDefault);TextViewtimeDefault=(TextView)findViewById(R.id.timeDefault);

DatePickerdp=(DatePicker)this.findViewById(R.id.datePicker);//Themonth,andjustthemonth,iszero-based.Add1fordisplay.dateDefault.setText("Datedefaultedto"+(dp.getMonth()+1)+"/"+dp.getDayOfMonth()+"/"+dp.getYear());//Andhere,subtract1fromDecember(12)tosetittoDecemberdp.init(2008,11,10,null);

TimePickertp=(TimePicker)this.findViewById(R.id.timePicker);

java.util.FormattertimeF=newjava.util.Formatter();timeF.format("Timedefaultedto%d:%02d",tp.getCurrentHour(),tp.getCurrentMinute());timeDefault.setText(timeF.toString());

tp.setIs24HourView(true);tp.setCurrentHour(newInteger(10));tp.setCurrentMinute(newInteger(10));}

Listing3-25setsthedateontheDatePickertoDecember10,2008.Notethatforthemonth,theinternalvalueiszero-based,whichmeansthatJanuaryis0andDecemberis11.FortheTimePicker,thenumberofhoursandminutesissetto10.Notealsothatthiscontrolsupports24-hourview.Ifyoudonotsetvaluesforthesecontrols,thedefaultvalueswillbethecurrentdateandtimeasknowntothedevice.

Finally,notethatAndroidoffersversionsofthesecontrolsasmodalwindows,suchasDatePickerDialogandTimePickerDialog.Thesecontrolsareusefulifyouwanttodisplaythecontroltotheuserandforcetheusertomakeaselection.We’llcoverdialogsinmoredetailinChapter8.

TheTextClockandAnalogClockControls

AndroidalsooffersTextClockandAnalogClockcontrols(seeFigure3-8).

Figure3-8.UsingtheAnalogClockandDigitalClock

Asshown,thetextclocksupportssecondsinadditiontohoursandminutes.TheanalogclockinAndroidisatwo-handedclock,withonehandforthehourindicatorandtheotherhandfortheminuteindicator.Toaddthesetoyourlayout,usetheXMLasshowninListing3-26.

Listing3-26.AddingaDigitalClockoranAnalogClockinXML

<TextClockandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:format12Hour="hh:mm:ssaa"android:format24Hour="kk:mm:ss"/>

<AnalogClockandroid:layout_width="wrap_content"android:layout_height="wrap_content"/>

Thesetwocontrolsarereallyjustfordisplayingthecurrenttime,astheydon’tletyoumodifythedateortime.Inotherwords,theyarecontrolswhoseonlycapabilityistodisplaythecurrenttime.Thus,ifyouwanttochangethedateortime,you’llneedtosticktotheDatePicker/TimePickerorDatePickerDialog/TimePickerDialog.Thenicepartaboutthesetwoclocks,though,isthattheywillupdatethemselveswithoutyouhavingtodoanything.Thatis,thesecondstickawayintheTextClock,andthehandsmoveontheAnalogClockwithoutanythingextrafromus.

TheMapViewControlWiththeintroductionofGooglePlayServices,Android’sapproachtodisplayingmap-baseddataunderwentsomechanges.However,thevastmajorityofdevelopersstillfavor

theoriginalMapViewControlforarangeofreasons—backwardcompatibility,simplicity,andsoon.Asthenamesuggests,thecom.google.android.maps.MapViewcontrolcandisplayamap.YoucaninstantiatethiscontroleitherviaXMLlayoutorcode,buttheactivitythatusesitmustextendMapActivity.MapActivitytakescareofmultithreadingrequeststoloadamap,performcaching,andsoon.

NoteStrictly,theMapViewispartoftheGoogleAPI,notthestockAndroidAPI.Inordertotestcodeetc.forMapView,ensureyouremulatoriscreatedagainstaversionoftheSDKwiththeGoogleAPIsincluded.

Listing3-27showsanexampleinstantiationofaMapView.

Listing3-27.CreatingaMapViewControlviaXMLLayout

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<com.google.android.maps.MapViewandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:enabled="true"android:clickable="true"android:apiKey="myAPIKey"/>

</LinearLayout>

We’lldiscussthelocation-basedservicesindetailinChapter19.Thisisalsowhereyou’lllearnhowtoobtainyourownmappingAPIkey.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch03_Controls.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoEclipsefromoneoftheseZIPfiles.

http://developer.android.com/resources/articles/index.htmlSeveral“LayoutTricks”–typetechnicalarticlesthatarewellworth

reading.TheygetintoperformanceaspectsofdesigningandbuildingUIsinAndroid.LookforotherarticlesinthislistrelatedtobuildingUIs.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutbuildinguserinterfaces:

HowXMLresourcesdefineUIappearances,andhowcodefillsinthedata

ThefullrangeofbasicUserInterfacecontrolsavailableinAndroid

Ahintofwhat’stocomewithListviewsinChapter4andLayoutsinChapter5.

Chapter4

AdaptersandListControlsInChapter3,weintroducedarangeofbasicUserInterfacecontrolswithwhichyoucanconstructAndroidapplications.IfyourecalltheexamplesonTextView,oneofthetypesofcontrolsweexploredwastheAutoCompleteTextView,whichwhencoupledwithasourceofdata—anadapter—wasabletoprompttheuserwitharangeofpredeterminedvalues.Inthischapter,we’llexploreadaptersfurther,andthewidertopicoflistcontrolsthatenableconstructionofmoreelaborateandsophisticatedscreendesigns.

UnderstandingAdaptersBeforewegetintothedetailsoflistcontrolsofAndroid,weneedtotalkaboutadapters.Listcontrolsareusedtodisplaycollectionsofdata.Butinsteadofusingasingletypeofcontroltomanageboththedisplayandthedata,Androidseparatesthesetworesponsibilitiesintolistcontrolsandadapters.Listcontrolsareclassesthatextendandroid.widget.AdapterViewandincludeListView,GridView,Spinner,andGallery(seeFigure4-1).

Figure4-1.AdapterViewclasshierarchy

AdapterViewitselfextendsandroid.widget.ViewGroup,whichmeansthatListView,GridView,andsoonarecontainercontrols.Inotherwords,listcontrolscontaincollectionsofchildviews.ThepurposeofanadapteristomanagethedataforanAdapterViewandtoprovidethechildviewsforit.Let’sseehowthisworksbyexaminingtheSimpleCursorAdapter.

GettingtoKnowSimpleCursorAdapterTheSimpleCursorAdapterisdepictedinFigure4-2.

Figure4-2.TheSimpleCursorAdapter

Thisisaveryimportantpicturetounderstand.OntheleftsideistheAdapterView;inthisexample,itisaListViewmadeupofTextViewchildren.Ontherightsideisthedata;inthisexample,it’srepresentedasaresultsetofdatarowsthatcamefromaqueryagainstacontentprovider.

TomapthedatarowstotheListView,theSimpleCursorAdapterneedstohaveachildlayoutresourceID.Thechildlayoutmustdescribethelayoutforeachofthedataelementsfromtherightsidethatshouldbedisplayedontheleftside.Alayoutinthiscaseisjustlikethelayoutswe’vebeenworkingwithforouractivities,butitonlyneedstospecifythelayoutofasinglerowofourListView.Forexample,ifyouhavearesultsetofinformationfromtheContactscontentprovider,andyouonlywanttodisplayeachcontactnameinyourListView,youwouldneedtoprovidealayouttodescribewhatthenamefieldshouldlooklike.IfyouwantedtodisplaythenameandanimagefromtheresultsetineachrowoftheListView,yourlayoutmustsayhowtodisplaythenameandtheimage.

Thisdoesnotmeanyoumustprovidealayoutspecificationforeveryfieldinyourresultset,nordoesitmeanyoumusthaveapieceofdatainyourresultsetforeverythingyouwanttoincludeineachrowoftheListView.Forexample,we’llshowyouinabithowyoucanhavecheckboxesinyourListViewforselectingrows,andthosecheckboxesdon’tneedtobesetfromdatainaresultset.We’llalsoshowyouhowtogettodataintheresultsetthatisnotpartoftheListView.Andalthoughwe’vejusttalkedaboutListViews,TextViews,cursors,andresultsets,pleasekeepinmindthattheadapterconceptismoregeneralthanthis.Theleftsidecanbeagallery,andtherightsidecanbeasimplearrayofimages.Butlet’skeepthingsfairlysimplefornowandlookatSimpleCursorAdapterinmoredetail.

ThesimplestconstructorofSimpleCursorAdapterlookslikethis:

SimpleCursorAdapter(Contextcontext,intchildLayout,Cursorc,String[]from,int[]to)

Thisadapterconvertsarowfromthecursortoachildviewforthecontainercontrol.ThedefinitionofthechildviewisdefinedinanXMLresource(childLayoutparameter).Notethatbecausearowinthecursormighthavemanycolumns,youtelltheSimpleCursorAdapterwhichcolumnsyouwanttoselectfromtherowbyspecifyinganarrayofcolumnnames(usingthefromparameter).

Similarly,becauseeachcolumnyouselectmustbemappedtoaViewinthelayout,youmustspecifytheIDsinthetoparameter.There’saone-to-onemappingbetweenthecolumnyouselectandaViewthatdisplaysthedatainthecolumn,sothefromandtoparameterarraysmusthavethesamenumberofelements.Aswementionedbefore,thechildviewcouldcontainothertypesofviews;theydon’thavetobeTextViews.YoucoulduseanImageView,forexample.

ThereisacarefulcollaborationgoingonbetweentheListViewandouradapter.WhentheListViewwantstodisplayarowofdata,itcallsthegetView()methodoftheadapter,passinginthepositiontospecifytherowofdatatobedisplayed.Theadapterrespondsbybuildingtheappropriatechildviewusingthelayoutthatwassetintheadapter’sconstructorandbypullingthedatafromtheappropriaterecordintheresultset.TheListView,therefore,doesn’thavetodealwithhowthedataexistsontheadapterside;itonlyneedstocallforchildviewsasneeded.Thisisacriticalpoint,becauseitmeansourListViewdoesn’tnecessarilyneedtocreateeverychildviewforeverydatarow.Itreallyonlyneedstohaveasmanychildviewsasarenecessaryforwhat’svisibleinthedisplaywindow.Ifonlytenrowsarebeingdisplayed,technicallytheListViewneedstohaveonlytenchildlayoutsinstantiated,eveniftherearehundredsofrecordsinourresultset.Inreality,morethantenchildlayoutsgetinstantiated,becauseAndroidusuallykeepsextrasonhandtomakeitfastertobringanewrowtovisibility.TheconclusionyoushouldreachisthatthechildviewsmanagedbytheListViewcanberecycled.We’lltalkmoreaboutthatalittlelater.

Figure4-2revealssomeflexibilityinusingadapters.Becausethelistcontrolusesanadapter,youcansubstitutevarioustypesofadaptersbasedonyourdataandchildview.Forexample,ifyouarenotgoingtopopulateanAdapterViewfromacontentproviderordatabase,youdon’thavetousetheSimpleCursorAdapter.Youcanoptforaneven“simpler”adapter—theArrayAdapter.

GettingtoKnowArrayAdapterTheArrayAdapteristhesimplestoftheadaptersinAndroid.ItspecificallytargetslistcontrolsandassumesthatTextViewcontrolsrepresentthelistitems(thechildviews).CreatinganewArrayAdaptercanlookassimpleasthis:

ArrayAdapter<String>adapter=newArrayAdapter<String>

(this,android.R.layout.simple_list_item_1,newString[]{"Dave","Satya","Dylan"});

Westillpassthecontext(this)andachildLayoutresourceID.Butinsteadofpassingafromarrayofdatafieldspecifications,wepassinanarrayofstringsastheactualdata.Wedon’tpassacursororatoarrayofViewresourceIDs.TheassumptionhereisthatourchildlayoutconsistsofasingleTextView,andthat’swhattheArrayAdapterwilluseasthedestinationforthestringsthatareinourdataarray.

Nowwe’regoingtointroduceaniceshortcutforthechildLayoutresourceID.Insteadofcreatingourownlayoutfileforthelistitems,wecantakeadvantageofpredefinedlayoutsinAndroid.NoticethattheprefixontheresourceforthechildlayoutresourceIDisandroid.Insteadoflookinginourlocal/resdirectory,Androidlooksinitsown.YoucanbrowsetothisfolderbynavigatingtotheAndroidSDKfolderandlookingunderplatforms/<android-version>/data/res/layout.Thereyou’llfindsimple_list_item_1.xmlandcanseeinsidethatitdefinesasimpleTextView.ThatTextViewiswhatourArrayAdapterwillusetocreateaview(initsgetView()method)togivetotheListView.Feelfreetobrowsethroughthesefolderstofindpredefinedlayoutsforallsortsofuses.We’llbeusingmoreoftheselater.

ArrayAdapterhasotherconstructors.IfthechildLayoutisnotasimpleTextView,youcanpassintherowlayoutresourceIDplustheresourceIDoftheTextViewtoreceivethedata.Whenyoudon’thaveaready-madearrayofstringstopassin,youcanusethecreateFromResource()method.Listings4-1,4-2,and4-3showanexampleinwhichwecreateanArrayAdapterforaspinner.

Listing4-1.ManifestFragmentforCreatinganArrayAdapterfromaString-ResourceFile

<Spinnerandroid:id="@+id/spinner"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

Listing4-2.CodeFragmentforCreatinganArrayAdapterfromaString-ResourceFile

Spinnerspinner=(Spinner)findViewById(R.id.spinner);

ArrayAdapter<CharSequence>adapter=ArrayAdapter.createFromResource(this,R.array.planets,android.R.layout.simple_spinner_item);

adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

spinner.setAdapter(adapter);

Listing4-3.TheActualString-ResourceFile

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/planets.xml--><resources><string-arrayname="planets"><item>Mercury</item><item>Venus</item><item>Earth</item><item>Mars</item><item>Jupiter</item><item>Saturn</item><item>Uranus</item><item>Neptune</item></string-array></resources>

ThefirstlistingistheXMLlayoutforaspinner.ThesecondJavalistinginshowshowyoucancreateanArrayAdapterwhosedatasourceisdefinedinastringresourcefile.UsingthismethodallowsyoutonotonlyexternalizethecontentsofthelisttoanXMLfilebutalsouselocalizedversions.We’lltalkaboutspinnersalittlelater,butfornow,knowthataspinnerhasaviewtoshowthecurrentlyselectedvalue,plusalistviewtoshowthevaluesthatcanbeselectedfrom.It’sbasicallyadrop-downmenu.Listing4-3istheXMLresourcefilecalled/res/values/planets.xml,whichisreadintoinitializetheArrayAdapter.

WorthmentioningisthattheArrayAdapterallowsfordynamicmodificationstotheunderlyingdata.Forexample,theadd()methodwillappendanewvalueontheendofthearray.Theinsert()methodwilladdanewvalueataspecifiedpositionwithinthearray.Andremove()takesanobjectoutofthearray.Youcanalsocallsort()toreorderthearray.Ofcourse,onceyou’vedonethis,thedataarrayisoutofsyncwiththeListView,sothat’swhenyoucallthenotifyDataSetChanged()methodoftheadapter.ThismethodwillresynctheListViewwiththeadapter.

ThefollowinglistsummarizestheadaptersthatAndroidprovides:

ArrayAdapter<T>:Thisisanadapterontopofagenericarrayofarbitraryobjects.It’smeanttobeusedwithaListView.

CursorAdapter:Thisadapter,alsomeanttobeusedinaListView,providesdatatothelistviaacursor.

SimpleAdapter:Asthenamesuggests,thisadapterisasimpleadapter.Itisgenerallyusedtopopulatealistwithstaticdata(possiblyfromresources).

ResourceCursorAdapter:ThisadapterextendsCursorAdapterandknowshowtocreateviewsfromresources.

SimpleCursorAdapter:Thisadapterextends

ResourceCursorAdapterandcreatesTextView/ImageViewviewsfromthecolumnsinthecursor.Theviewsaredefinedinresources.

We’vecoveredenoughofadapterstostartshowingyousomerealexamplesofworkingwithadaptersandlistcontrols(alsoknownasAdapterViews).Let’sgettoit.

UsingAdapterswithAdapterViewsNowthatyou’vebeenintroducedtoadapters,itistimetoputthemtoworkforus,providingdataforlistcontrols.Inthissection,we’regoingtofirstcoverthebasiclistcontrol,theListView.Then,we’lldescribehowtocreateyourowncustomadapter,andfinally,we’lldescribetheothertypesoflistcontrols:GridViews,spinners,andthegallery.

TheBasicListControl:ListViewTheListViewcontroldisplaysalistofitemsvertically.Thatis,ifwe’vegotalistofitemstoviewandthenumberofitemsextendsbeyondwhatwecancurrentlyseeinthedisplay,wecanscrolltoseetherestoftheitems.YougenerallyuseaListViewbywritinganewactivitythatextendsandroid.app.ListActivity.ListActivitycontainsaListView,andyousetthedatafortheListViewbycallingthesetListAdapter()method.

Aswedescribedpreviously,adapterslinklistcontrolstothedataandhelppreparethechildviewsforthelistcontrol.ItemsinaListViewcanbeclickedtotakeimmediateactionorselectedtoactonthesetofselecteditemslater.We’regoingtostartreallysimpleandthenaddfunctionalityaswego.

DisplayingValuesinaListViewFigure4-3showsaListViewcontrolinitssimplestform.

Figure4-3.UsingtheListViewcontrol

Forthisexercise,wewillplaceaListViewintoadefaultAndroidlayout,withnospecialtweaksorchanges,soyoucanseehowtheyfitwithinatypicalmainlayoutXMLfile.Listing4-4showstheJavacodeforouractivity.

Listing4-4.AddingItemstoaListView

publicclassMainActivityextendsActivity{privateListViewlistView1;privateArrayAdapter<String>listAdapter1;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

listView1=(ListView)findViewById(R.id.listView1);

String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));

listAdapter1=newArrayAdapter<String>(this,android.R.id.text1,colorArrayList);

listView1.setAdapter(listAdapter1);

}...}

Listing4-2createsaListViewcontrolpopulatedwiththelistofcolorswespecifyinanarray,someColors.Inourexample,wetakethecontentsofthearrayandmaptheStringcolornamestoaTextViewcontrol(android.R.id.text1).Afterthat,wecreateanarrayadapterandsetthelist’sadapter.TheadapterclasshasthesmartstotaketherowsinwhateverdatasourceyouprovidetopopulatetheUI.

WecouldhavetakenadvantageoftheverybasicListActivitysupplyingthemainlayout,astherearenootherUIelementsorcomplexitytotakecareof.Howeverwe’vechosentodeploytheListViewwithinatypicalnewprojectandutilizethebasicactivity.We’realsousinganAndroid-providedlayoutforourchildview(resourceIDandroid.R.layout.simple_list_item_1),whichcontainsanAndroid-providedTextView(resourceIDandroid.R.id.text1).Allinall,prettysimpletosetup.

Wecanextendthisexample,andyourunderstanding,byshowinghowtoreplacetheAndroid-providedlayoutforthechildviewwithoneofourowndesign.Createanewemptyfileintheres/layoutfolderofyourprojectandnameitsimple_list_row.xml.Listing4-5showstheXMLforourownlayoutforasimpleTextViewtorepresenteachlinetoberenderedinourListView(oranyotherlayoutthatreferstothissimple_list_rowlayout).

Listing4-5.CreatingaCustomTextViewChildViewforListRendering

<TextViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rowTextView"android:layout_width="fill_parent"android:layout_height="wrap_content"android:padding="12dp"android:textSize="24sp"></TextView>

WeneedonlychangethereferencethatbindsthechosenlayoutforuseintheListViewinourcode,tousethenewsimple_list_rowlayout,likeso:

listAdapter1=newArrayAdapter<String>(this,R.layout.simple_list_row,colorArrayList);

Notethatwhenwerefertoourowncustomlayoutinthisway,wedroptheleading“android”reference.Wecannowrunthisexampletoseethecompleteeffect,asshowninFigure4-4.

Figure4-4.TheListViewexampleinaction

ClickableItemsinaListViewOfcourse,whenyourunthisexample,you’llseethatyou’reabletoscrollupanddownthelisttoseeallyourcolornames,butthat’saboutit.Whatifwewanttodosomethingalittlemoreinterestingwiththisexample,likehavetheapplicationrespondwhenauserclicksoneoftheitemsinourListView?Listing4-6showsamodificationtoourexampletoacceptuserinput.

Listing4-6.AcceptingUserInputonaListView

publicclassMainActivityextendsActivity{

privateListViewlistView1;privateArrayAdapter<String>listAdapter1;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

listView1=(ListView)findViewById(R.id.listView1);

String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));

listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,colorArrayList);

listView1.setAdapter(listAdapter1);

listView1.setOnItemClickListener(newOnItemClickListener(){

@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}...}

OuractivityisnowimplementingtheOnItemClickListenerinterface,whichmeanswe’llreceiveacallbackwhentheuserclickssomethinginourListView.AsyoucanseebyouronItemClick()method,wegetalotofinformationaboutwhatwasclicked,includingtheviewreceivingtheclick,thepositionoftheclickeditemintheListView,andtheIDoftheitemaccordingtoouradapter.WecastaccordinglybeforecallingthemakeText()methodtoworkwiththecolor’sname.ThepositionvaluerepresentswherethisitemisinrelationtotheoveralllistofitemsintheListView,andit’szero-based.Therefore,thefirstiteminthelistisatposition0.

TheIDvaluedependsentirelyontheadapterandthesourceofthedata.Inourexample,wehappentobequeryingstringswiththenamesofcolorsinanarray,sotheIDaccordingtothisadapteristhepositionoftheentryinthearrayfromthecontentprovider.Butyourdatasourceinothersituationsmaynotbeasstraightforwardasthis,soyoushouldnot

thinkthatyoucanalwaysknowthingslikeorderinginadvanceaswe’vedoneinthisexample.IfwewereusinganSimpleCursorAdapterthathadreaditsvaluesfromthesystem’sContactsdatabase,theIDgiventouswillbetheunderlying_IDoftherecord,andthatcouldbeanyvaluedependingontheageofthecontactinthesystem.

WhenwediscussedArrayAdaptersbefore,wementionedthenotifyDataSetChanged()methodtohavetheadapterupdatetheListViewifthedatahaschanged.Someadapters,suchastheSimpleCursorAdapter,areawareofupdatesthathappentounderlyingdatasourcessuchastheContactscontentproviderandwilldynamicallyupdateListViewcontentsforyoubasedonchanges.WithArrayAdapters,however,youwillneedtoinvokethenotifyDataSetChanged()methodyourself.

Thatwasprettyeasytodo.WegeneratedourownListViewofcolornames,andbyclickingacolorweshowedamessagetotheuser.Butwhatifwewanttoselectabunchofnamesfirstandthendosomethingwiththesubsetofpeople?Forthenextexampleapplication,we’regoingtomodifythelayoutofalistitemtoincludeacheckbox,andwe’regoingtoaddabuttontotheUItothenactonthesubsetofselecteditems.

AddingOtherControlswithaListViewIfyouwantadditionalcontrolsinyourmainlayout,youcanprovideyourownlayoutXMLfile,putinaListView,andaddotherdesiredcontrols.Forexample,youcouldaddabuttonbelowtheListViewintheUItosubmitanactionontheselecteditems,asshowninFigure4-5.

Figure4-5.Anadditionalbuttonthatletstheusersubmittheselecteditem(s)

ThemainlayoutforthisexampleisinListing4-7,anditcontainstheUIdefinitionoftheactivity—theListViewandtheButton.

Listing4-7.OverridingtheListViewReferencedbyOurActivity

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.artifexdigital.android.listviewdemo3.MainActivity">

<ListViewandroid:id="@+id/listView1"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"/>

<Buttonandroid:id="@+id/button1"android:layout_width="match_parent"android:layout_height="wrap_content"android:onClick="doClick"android:text="Submitselection"/>

</LinearLayout>

NoticethewaywehavetospecifytheheightandweightoftheListViewinLinearLayout.WewantourbuttontoappearonthescreenatalltimesnomatterhowmanyitemsareinourListView,andwedon’twanttobescrollingallthewaytothebottomofthepagejusttofindthebutton.Toaccomplishthis,wesetthelayout_heighttowrap_contentandthenuselayout_weighttosaythatthiscontrolshouldtakeupallavailableroomfromtheparentcontainer.ThistrickallowsroomforthebuttonandretainsourabilitytoscrolltheListView.We’lltalkmoreaboutlayoutsandweightslaterinthischapter.

TheactivityimplementationwouldthenlooklikeListing4-8.

Listing4-8.ReadingUserInputfromtheListActivity

publicclassMainActivityextendsActivity{

privateListViewlistView1;privateArrayAdapter<String>listAdapter1;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

listView1=(ListView)findViewById(R.id.listView1);

String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));

listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_checked,colorArrayList);

listView1.setAdapter(listAdapter1);

listView1.setChoiceMode(listView1.CHOICE_MODE_MULTIPLE);

listView1.setOnItemClickListener(newOnItemClickListener(){

@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}

publicvoiddoClick(Viewview){intcount=listView1.getCount();SparseBooleanArrayviewItems=listView1.getCheckedItemPositions();for(inti=0;i<count;i++){if(viewItems.get(i)){StringselectedColor=(String)listView1.getItemAtPosition(i);Log.v("ListViewDemo",selectedColor+"ischeckedatposition"+i);}}}}

Withinthesetupoftheadapter,we’repassinganotheroftheAndroid-providedviewsforaListViewlineitem(android.R.layout.simple_list_item_checked),whichresultsineachrowhavingaTextViewandaCheckBox.Ifyoulookinsidethislayoutfile,youwillseeanothersubclassofTextView,thisonecalledCheckedTextView.ThisspecialtypeofTextViewisintendedforusewithListViews.See,wetoldyouthereweresomeinterestingthingsinthatAndroidlayoutfolder!YouwillseethattheIDoftheCheckedTextViewistext1,whichiswhatweneededtopassinourviewsarraytotheconstructoroftheSimpleCursorAdapter.

Becausewewanttheusertobeabletoselectourrows,wesetthechoicemodetoCHOICE_MODE_MULTIPLE.Bydefault,thechoicemodeisCHOICE_MODE_NONE.TheotherpossiblevalueisCHOICE_MODE_SINGLE.Ifyouwanttousethatchoicemodeforthisexample,youwouldwanttouseadifferentlayout,mostlikelyandroid.R.layout.simple_list_item_single_choice.

Inthisexample,we’veimplementedabasicbuttonthatcallsthedoClick()methodof

ouractivity.Tokeepthingssimple,wejustwanttowriteouttoLogCatthenamesoftheitemsthatwerecheckedbytheuser.Thegoodnewsisthatthesolutionisprettyeasy;thebadnewsisthatAndroidhasevolvedsothebestsolutiondependsonwhichversionofAndroidyou’retargeting.TheListViewsolutionwe’veshownherehasworkedsinceAndroid1(althoughwetooktheAndroid1.6shortcutonthebuttoncallback).Thatis,thegetCheckedItemPositions()methodisold,butitstillworks.Thereturnvalueisanarraythatcantellyouwhetheranitemhasbeenchecked.So,weiteratethroughthearray.viewItems.get(i)willreturntrueifthecorrespondingrowinourListViewhasbeenchecked.OurdataisaccessibledirectlyfromtheListView,usingthegetItemAtPosition()methodoftheListView.Inourcase,theobjectreturnedfromgetItemAtPosition()wouldturnouttobeaStringobject.Aswesaidbefore,inothersituations,wemightgetsomeothertypeofobject,suchasaCursorWrapper,whenworkingwithsomespecificcontentprovidersliketheContactsproviderdiscussedlaterinthisbook.Youhavetounderstandyourdatasourceandyouradaptertoknowwhattoexpect.

IfwegoaheadandhittheSubmitSelectionbuttonshowninFigure4-5,wecanwatchthelogcatwindowinEclipseorAndroidStudioasitemitsthedatafromourselectionasimplementedinthedoClick()method.ThisisshowninFigure4-6.

Figure4-6.UsinguserinputfromaListViewforfurtherprocessing

AnotherWaytoReadSelectionsfromaListViewAndroid1.6introducedanothermethodforretrievingalistofthecheckedrowsfromaListView:getCheckItemIds().Then,inAndroid2.2,thismethodwasdeprecatedandreplacedwithgetCheckedItemIds().Itwasasubtlenamechange,butthewayyouusethemethodisbasicallythesame.Listing4-9showstheJavacodechangeswe’dmaketoreflectthisevolutionofdealingwithcheckeditemsinalist.FortheXMLlayoutoflist.xml,wecancontinuetousethefileinListing4-7.

Listing4-9.AnotherWayofReadingUserInputfromtheListActivity

publicclassMainActivityextendsActivity{privateListViewlistView1;privateArrayAdapter<String>listAdapter1;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

listView1=(ListView)findViewById(R.id.listView1);

String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));

listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_checked,colorArrayList);

listView1.setAdapter(listAdapter1);

listView1.setChoiceMode(listView1.CHOICE_MODE_MULTIPLE);

listView1.setOnItemClickListener(newOnItemClickListener(){

@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}

...

publicvoiddoClick(Viewview){if(!listAdapter1.hasStableIds()){Log.v(TAG,"Dataisnotstable");return;}long[]viewItems=listView1.getCheckedItemIds();for(inti=0;i<viewItems.length;i++){StringselectedColor=(String)listView1.getItemAtPosition(i);Log.v("ListViewDemo",selectedColor+"ischecked

atposition"+i);}}}}

Inthisexampleapplication,whenweclickthebutton,ourcallbackcallsthemethodgetCheckedItemIds().Whereasinourlastexample,wegotanarrayofpositionsofthecheckeditemsintheListView,thistimewegetanarrayofIDsoftherecordsfromtheadapterthathavebeencheckedintheListView.WecanbypasstheListViewandthecursornow,becausetheIDscanbeusedtodrivewhateveractionwedesire.

We’veshownyouhowtoworkwithListViewsfromavarietyofscenarios.We’veshownthatadaptersdoalotoftheworktosupportaListView.Next,we’llcovertheothertypesoflistcontrols,startingwiththeGridView.

TheGridViewControlMostwidgettoolkitsofferoneormoregrid-basedcontrols.AndroidhasaGridViewcontrolthatcandisplaydataintheformofagrid.Notethatalthoughweusethetermdatahere,thecontentsofthegridcanbetext,images,andsoon.

TheGridViewcontroldisplaysinformationinagrid.TheusagepatternfortheGridViewistodefinethegridintheXMLlayout(seeListings4-10and4-11)andthenbindthedatatothegridusinganandroid.widget.ListAdapter.

Listing4-10.DefinitionofaGridViewinanXMLLayout

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.artifexdigital.android.gridviewdemo.MainActivity">

<GridViewandroid:id="@+id/gridView1"android:layout_width="fill_parent"android:layout_height="fill_parent"android:padding="10dp"android:verticalSpacing="10dp"android:horizontalSpacing="10dp"android:numColumns="auto_fit"android:columnWidth="100dp"android:stretchMode="columnWidth"android:gravity="center"/>

</RelativeLayout>

Listing4-11.JavaImplementationfortheGridView

publicclassMainActivityextendsActivity{privateGridViewgridView1;privateArrayAdapter<String>listAdapter1;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

gridView1=(GridView)findViewById(R.id.gridView1);

String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};

ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));

listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,colorArrayList);

gridView1.setAdapter(listAdapter1);

}

}

Listing4-10definesasimpleGridViewinanXMLlayout.Thegridisthenloadedintotheactivity’scontentview.ThegeneratedUIisshowninFigure4-7.

Figure4-7.AGridViewpopulatedwithcolors

ThegridshowninFigure4-7displaysthenamesofthecolorsfromourarray.WehavedecidedtoshowaTextViewwiththecolornames,butyoucouldeasilygenerateagridfilledwithimagesorothercontrols.We’veagaintakenadvantageofpredefinedlayoutsinAndroid.Infact,thisexamplelooksverymuchlikeListing4-7exceptforafewimportantdifferences.WemustcallsetContentView()tosetthelayoutforourGridView;therearenodefaultviewstofallbackon.Andtosettheadapter,wecallsetAdapter()ontheGridViewobjectinsteadofcallingsetListAdapter()onActivity.

You’venodoubtnoticedthattheadapterusedbythegridisaListAdapter.Listsaregenerallyone-dimensional,whereasgridsaretwo-dimensional.Wecanconclude,then,thatthegridactuallydisplayslist-orienteddata.Anditturnsoutthatthelistisdisplayedbyrows.Thatis,thelistgoesacrossthefirstrow,thenacrossthesecondrow,andsoon.

Asbefore,wehavealistcontrolthatworkswithanadaptertohandlethedatamanagementandthegenerationofthechildviews.ThesametechniquesweusedbeforeshouldworkjustfinewithGridViews.Oneexceptionrelatestomakingselections:thereisnowaytospecifymultiplechoicesinaGridView,aswedidinListing4-7.

TheSpinnerControlTheSpinnercontrolislikeadrop-downmenu.Itistypicallyusedtoselectfromarelativelyshortlistofchoices.Ifthechoicelististoolongforthedisplay,ascrollbarisautomaticallyaddedforyou.YoucaninstantiateaSpinnerviaXMLlayoutassimplyasthis:

<Spinnerandroid:id="@+id/spinner"android:prompt="@string/spinnerprompt"android:layout_width="wrap_content"android:layout_height="wrap_content"

/>

Althoughaspinneristechnicallyalistcontrol,itwillappeartoyoumorelikeasimpleTextViewcontrol.Inotherwords,onlyonevaluewillbedisplayedwhenthespinnerisatrest.Thepurposeofthespinneristoallowtheusertochoosefromasetofpredeterminedvalues:whentheuserclicksthesmallarrow,alistisdisplayed,andtheuserisexpectedtopickanewvalue.Populatingthislistisdoneinthesamewayastheotherlistcontrols:withanadapter.

Becauseaspinnerisoftenusedlikeadrop-downmenu,itiscommontoseetheadaptergetthelistchoicesfromaresourcefile.AnexamplethatsetsupaspinnerusingaresourcefileisshowninListing4-12.Noticethenewattributecalledandroid:promptforsettingapromptatthetopofthelisttochoosefrom.Theactualtextforourspinnerpromptisinour/res/values/strings.xmlfile.Asyoushouldexpect,theSpinnerclasshasamethodforsettingthepromptincodeaswell.

Listing4-12.CodetoCreateaSpinnerfromaResourceFile

publicclassSpinnerActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.spinner);

Spinnerspinner=(Spinner)findViewById(R.id.spinner);

ArrayAdapter<CharSequence>adapter=ArrayAdapter.createFromResource(this,R.array.planets,android.R.layout.simple_spinner_item);

adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

spinner.setAdapter(adapter);}}

Youmayrecallseeingtheplanets.xmlfileinListing4-1.WeshowinthisexamplehowaSpinnercontroliscreated;theadapterissetupandthenassociatedtothespinner.SeeFigure4-8forwhatthislookslikeinaction.

Figure4-8.Aspinnerforchoosingaplanet

Oneofthedifferencesfromourearlierlistcontrolsisthatwe’vegotanextralayouttocontendwithwhenworkingwithaspinner.TheleftsideofFigure4-8showsthenormalmodeofaspinner,wherethecurrentselectionisshown.Inthiscase,thecurrentselectionisSaturn.Nexttothewordisadownward-pointingarrowindicatingthatthiscontrolisaspinnerandcanbeusedtopopupalisttoselectadifferentvalue.Thefirstlayout,suppliedasaparametertotheArrayAdapter.createFromResource()method,defineshowthespinnerlooksinnormalmode.OntherightsideofFigure4-8,weshowthespinnerinthepop-uplistmode,waitingfortheusertochooseanewvalue.ThelayoutforthislistissetusingthesetDropDownViewResource()method.Againinthisexample,we’reusingAndroid-providedlayoutsforthesetwoneeds,soifyouwanttoinspectthedefinitionofeitheroftheselayouts,youcanvisittheAndroidres/layoutfolder.Andofcourse,youcanspecifyyourownlayoutdefinitionforeitherofthesetogettheeffectyouwant.

TheGalleryControlTheGallerycontrolisahorizontallyscrollablelistcontrolthatalwaysfocusesatthecenterofthelist.Thiscontrolgenerallyfunctionsasaphotogalleryintouchmode.YoucaninstantiateaGalleryviaeitherXMLlayoutorcode:

<Galleryandroid:id="@+id/gallery"

android:layout_width="fill_parent"android:layout_height="wrap_content"/>

TheGallerycontrolistypicallyusedtodisplayimages,soyouradapterislikelygoingtobespecializedforimages.We’llshowyouacustomimageadapterinnextsectiononcustomadapters.Visually,agallerylookslikeFigure4-9.

Figure4-9.Agallerywithimagesofmanatees

SummaryInthischapter,we’veexpandedyourunderstandingandproficiencywithUIcomponentsinthefollowingways:

ThemainlistcontrolsavailableinAndroid

Howtouseadapterstopopulatethedatainalistcontrol

Chapter5

BuildingMoreAdvancedUILayoutsInthepreviouschapterswereviewedmanyofthestandardlayoutsprovidedwithAndroid,whichcoverabroadarrayofpossibleUIapproaches.WhenthestocklayoutsofferedbyAndroiddon'tquitedowhatyouwant,wheredoyouturn?Inthischapter,wewillquicklyexplorehowAndroidprovidesyouwiththeabilitytobuildyourowncustomlayoutsandmanagetherelatedadaptersforpopulatingthemwithusefuldata.

CreatingCustomAdaptersStandardadaptersinAndroidareeasytouse,buttheyhavesomelimitations.Toaddressthis,AndroidprovidesanabstractclasscalledBaseAdapterthatyoucanextendifyouneedacustomadapter.Youwoulduseacustomadapterifyouhadspecialdata-managementneedsorifyouwantedmorecontroloverhowtodisplaychildviews.Youmightalsouseacustomadaptertoimproveperformancebyusingcachingtechniques.We’regoingtoshowyouhowtobuildacustomadapternext.

Listing5-1showswhattheXMLlayoutandtheJavacodecouldlooklikeforacustomadapter.Forthisnextexample,ouradapterisgoingtodealwithimagesofmanatees,sowe’llcallitManateeAdapter.We’regoingtocreateitinsideofanactivityaswell.

Listing5-1.OurCustomAdapter:ManateeAdapter

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisat/res/layout/gridviewcustom.xml--><GridViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/gridview"android:layout_width="fill_parent"android:layout_height="fill_parent"android:padding="10dip"android:verticalSpacing="10dip"android:horizontalSpacing="10dip"android:numColumns="auto_fit"android:gravity="center"/>

JavaImplementation

publicclassGridViewCustomAdapterextendsActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState)

{super.onCreate(savedInstanceState);setContentView(R.layout.gridviewcustom);GridViewgv=(GridView)findViewById(R.id.gridview);ManateeAdapteradapter=newManateeAdapter(this);gv.setAdapter(adapter);}

publicstaticclassManateeAdapterextendsBaseAdapter{privatestaticfinalStringTAG="ManateeAdapter";privatestaticintconvertViewCounter=0;privateContextmContext;privateLayoutInflatermInflater;staticclassViewHolder{ImageViewimage;}

privateint[]manatees={R.drawable.manatee00,R.drawable.manatee01,R.drawable.manatee02,//...manymoremanateeshere-seethesamplecodefolderR.drawable.manatee32,R.drawable.manatee33};

privateBitmap[]manateeImages=newBitmap[manatees.length];privateBitmap[]manateeThumbs=newBitmap[manatees.length];

publicManateeAdapter(Contextcontext){Log.v(TAG,"ConstructingManateeAdapter");this.mContext=context;mInflater=LayoutInflater.from(context);

for(inti=0;i<manatees.length;i++){manateeImages[i]=BitmapFactory.decodeResource(context.getResources(),manatees[i]);manateeThumbs[i]=Bitmap.createScaledBitmap(manateeImages[i],100,100,false);}}

@OverridepublicintgetCount(){Log.v(TAG,"ingetCount()");returnmanatees.length;

}

publicintgetViewTypeCount(){Log.v(TAG,"ingetViewTypeCount()");return1;}

publicintgetItemViewType(intposition){Log.v(TAG,"ingetItemViewType()forposition"+position);return0;}

@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){ViewHolderholder;

Log.v(TAG,"ingetViewforposition"+position+",convertViewis"+((convertView==null)?"null":"beingrecycled"));

if(convertView==null){convertView=mInflater.inflate(R.layout.gridimage,null);convertViewCounter++;Log.v(TAG,convertViewCounter+"convertViewshavebeencreated");holder=newViewHolder();holder.image=(ImageView)convertView.findViewById(R.id.gridImageView);convertView.setTag(holder);}else{holder=(ViewHolder)convertView.getTag();}

holder.image.setImageBitmap(manateeThumbs[position]);

returnconvertView;}

@OverridepublicObjectgetItem(intposition){Log.v(TAG,"ingetItem()forposition"+position);

returnmanateeImages[position];}

@OverridepubliclonggetItemId(intposition){Log.v(TAG,"ingetItemId()forposition"+position);returnposition;}}}

Whenyourunthisapplication,youshouldseeadisplaythatlookslikeFigure5-1.

Figure5-1.AGridViewwithimagesofmanatees

Thereisalottoexplaininthisexample,eventhoughitlooksrelativelysimple.We’llstartwithourActivityclass,whichlooksalotliketheoneswe’vebeenworkingwiththroughoutthissectionofthechapter.There’samainlayoutfromgridviewcustom.xml,whichcontainsjustaGridViewdefinition.WeneedtogetareferencetotheGridViewfrominsidethelayout,sowedefineandsetgv.We

instantiateourManateeAdapter,passingitourcontext,andwesettheadapteronourGridView.Thisisprettystandardstuffsofar,althoughyou’venodoubtnoticedthatourcustomadapterdoesn’tusenearlyasmanyparametersaspredefinedadapterswhenbeingcreated.Thisismainlybecausewe’reincompletecontroloverthisparticularadapter,andwe’reusingitwithonlythisapplication.Ifweweremakingthisadaptermoregeneral,wewouldmostlikelybesettingmoreparameters.Butlet’skeepgoing.OurjobinsideanadapteristomanagethepassingofdataintoAndroidViewobjects.TheViewobjectswillbeusedbythelistcontrol(aGridViewinthiscase).Thedatacomesfromsomedatasource.Intheearlierexamples,thedatacameviaacursorobjectthatwaspassedintotheadapter.Inourcustomcasehere,ouradapterknowsallaboutthedataandwhereitcomesfrom.ThelistcontrolwillaskforthingssoitknowshowtobuildtheUI.Itisalsokindenoughtopassinviewsforrecyclingwhenithasaviewitnolongerneeds.Itmayseemabitstrangetothinkthatouradaptermustknowhowtoconstructviews,butintheend,itallmakessense.

WhenweinstantiateourcustomadapterManateeAdapter,itiscustomarytopassinthecontextandfortheadaptertoholdontoit.Itisoftenveryusefultohaveitavailablewhenneeded.Thesecondthingwewanttodoinouradapteristohangontotheinflater.Thiswillhelpperformancewhenweneedtocreateanewviewtoreturntothelistcontrol.ThethirdthingthatistypicalinanadapteristocreateaViewHolderobject,tocontaintheViewobjectsforthedatawearemanaging.Takingthisapproachalsoactsasaperformanceoptimization,savingusfromrepeatedlylookinguptheViews.Forthisexample,wearesimplystoringanImageView,butifwehadadditionalfieldstodealwith,wewouldaddthemintothedefinitionofViewHolder.Forexample,ifwehadaListViewwhereeachrowcontainedanImageViewandtwoTextViews,ourViewHolderwouldhaveanImageViewandtwoTextViews.

Becausewe’redealingwithimagesofmanateesinthisadapter,wesetupanarrayoftheirresourceIDstobeusedduringconstructiontocreatebitmaps.Wealsodefineanarrayofbitmapstouseasourdatalist.

AsyoucanseefromourManateeAdapterconstructor,wesavethecontext,createandhangontoaninflater,andthenweiteratethroughtheimageresourceIDsandbuildanarrayofbitmaps.Thisbitmaparraywillbeourdata.

Asyoulearnedpreviously,settingtheadapterwillcauseourGridViewtocallmethodsontheadaptertosetitselfupwithdatatodisplay.Forexample,ourGridViewgvwillcalltheadapter’sgetCount()methodtodeterminehowmanyobjectstherearefordisplaying.ItwillalsocallthegetViewTypeCount()methodtodeterminehowmanydifferenttypesofviewscouldbedisplayedwithintheGridView.Forourpurposesinthisexample,wesetthisto1.However,ifwehadaListViewandwantedtoputseparatorsinbetweenregularrowsofdata,wewouldhavetwotypesofviewsandwouldneedtoreturn2fromgetViewTypeCount().Youcouldhaveasmanydifferentviewtypesasyoulike,aslongasyouappropriatelyreturnthecorrectcountfromthismethod.RelatedtothismethodisgetItemViewType().Wejustsaidthatwecouldhavemorethanonetypeofviewtoreturnfromtheadapter,buttokeepthingssimpler,

getItemViewType()needstoreturnonlyanintegervaluetoindicatewhichofourviewtypesisataparticularpositioninthedata.Therefore,ifwehadtwotypesofviewstoreturn,getItemViewType()wouldneedtoreturneither0or1toindicatewhichtype.Ifwehavethreetypesofviews,thismethodneedstoreturn0,1,or2.

IfouradapterisdealingwithseparatorsinaListView,itmusttreattheseparatorsasdata.Thatmeansthereisapositioninthedatathatistakenupbyaseparator.WhengetView()iscalledbyalistcontroltoretrievetheappropriateviewforthatposition,getView()willneedtoreturnaseparatorasaviewinsteadofregulardataasaview.AndwhenaskedingetItemViewType()fortheviewtypeforthatposition,weneedtoreturntheappropriateintegervaluethatwe’vedecidedmatchesthatviewtype.TheotherthingyoushoulddoifusingseparatorsistoimplementtheisEnabled()method.Thisshouldreturntrueforlistitemsandfalseforseparatorsbecauseseparatorsshouldnotbeselectableorclickable.

ThemostinterestingmethodinManateeAdapteristhegetView()methodcall.OncetheGridViewhasdeterminedhowmanyitemsareavailable,itstartstoaskforthedata.Now,wecantalkaboutrecyclingviews.Alistcontrolcanonlyshowasmanychildviewsonthedisplayaswillfit.Thatmeansthere’snopointincallinggetView()foreverypieceofdataintheadapter;itonlymakessensetocallgetView()forasmanyitemsascanbedisplayed.Asgvgetschildviewsbackfromtheadapter,itisdetermininghowmanywillfitonthedisplay.Whenthedisplayisfullofchildviews,gvcanstopcallinggetView().

IfyoulookatLogCatafterstartingthisexampleapplication,youwillseethevariouscalls,butyouwillalsoseethatgetView()stopsbeingcalledbeforeallimageshavebeenrequested.IfyoustartscrollingupanddowntheGridView,youwillseemorecallstogetView()inLogCat,andyouwillnoticethat,oncewe’vecreatedacertainnumberofchildviews,getView()isbeingcalledwithconvertViewsettosomething,notnull.Thismeanswe’renowrecyclingchildviews—andthat’sverygoodforperformance.

IfwegetanonnullconvertViewvaluefromgvingetView(),itmeansgvisrecyclingthatview.Byreusingtheviewpassedin,weavoidhavingtoinflateanXMLlayout,andweavoidhavingtofindtheImageView.BylinkingaViewHolderobjecttotheViewthatwereturn,wecanbemuchfasteratrecyclingtheviewthenexttimeitcomesbacktous.AllwehavetodoingetView()isreacquiretheViewHolderandassigntherightdataintotheview.

Forthisexample,wewantedtoshowthatthedataplacedintotheviewisnotnecessarilyexactlywhatexistsinthedata.ThecreateScaledBitmap()methodiscreatingasmallerversionofthedatafordisplaypurposes.ThepointisthatourlistcontroldoesnotcallthegetItem()method.Thismethodwouldbecalledbyourothercodethatwantstodosomethingwiththedataiftheuseractsonthelistcontrol.Onceagain,foranyadapter,itisveryimportantthatyouunderstandwhatitisdoing.Youdon’tnecessarilywanttorelyondataintheviewfromthelistcontrol,ascreatedbygetView()intheadapter.Sometimes,youwillneedtocalltheadapter’sgetItem()methodtogettheactualdatatobeoperatedon.Andsometimes,aswedidintheearlierListView

examples,you’llwanttogotoacursorforthedata.Italldependsontheadapterandwherethedataisultimatelycomingfrom.AlthoughweusedthecreateScaledBitmap()methodinourexample,Android2.2introducedanotherclassthatmighthavebeenhelpfulhere:ThumbnailUtils.Thisclasshassomestaticmethodsforgeneratingthumbnailimagesfrombitmapsandvideos.

ThelastthingtopointoutfromthisexampleisthegetItemId()methodcall.InourearlierexampleswithListViewsandcontacts,theitemIDwasthe_IDvaluefromthecontentprovider.Forthisexample,wedon’treallyneedtouseanythingotherthanpositionfortheitemID.ThepointofitemIDsistoprovideamechanismtorefertothedataseparatelyfromitsposition.Thisisespeciallytruewhenthedatahasalifeawayfromthisadapter,asisthecasewithourcontacts.Whenwehavethiskindofdirectcontroloverthedata,aswedowithourimagesofmanatees,andweunderstandhowtogettotheactualdatainourapplication,itisacommonshortcuttosimplyusepositionastheitemID.Thisisparticularlytrueinourcase,becausewedon’tevenallowaddingorremovalofdata.

OtherControlsinAndroidTherearemany,manycontrolsinAndroidthatyoucanuse.We’vecoveredquiteafewsofar,andmorewillbecoveredinlaterchapters(suchasMapViewinChapter19andVideoViewandMediaControllerinChapter20).Youwillfindthattheothercontrols,becausethey’realldescendedfromView,havealotincommonwhattheoneswe’vecoveredhere.Fornow,we’lljustmentionafewofthecontrolsyoumightwanttoexplorefurtheronyourown.

ScrollViewisacontrolforsettingupaViewcontainerwithaverticalscrollbar.Thisisusefulwhenyouhavetoomuchtofitontoasinglescreen.

TheProgressBarandRatingBarcontrolsarelikesliders.Thefirstshowstheprogressofsomeoperationvisually(perhapsafiledownloadormusicplaying),andthesecondshowsaratingscaleofstars.

TheChronometercontrolisatimerthatcountsup.There’saCountDownTimerclassifyouwantsomethingtohelpyoudisplayacountdowntimer,butit’snotaViewclass.

TheSwitchcontrol,whichfunctionslikeaToggleButtonbutvisuallyhasaside-to-sidepresentation,wasintroducedinAndroid4.0,alongwiththeSpaceview,alightweightviewthatcanbeusedinlayoutstomoreeasilycreatespacesbetweenotherviews.

WebViewisaveryspecialviewfordisplayingHTML.Itcandoalotmorethanthat,includinghandlingcookiesandJavaScriptandlinkingtoJavacodeinyourapplication.Butbeforeyougoimplementingawebbrowserinsideyourapplication,youshouldcarefullyconsiderinvokingtheon-devicewebbrowsertoletitdoallthatheavylifting.

Thatcompletesourintroductionofcontrolsinthischapter.We’llnowmoveontostylesandthemesformodifyingthelookandfeelofourcontrolsandthentolayoutsforarrangingourcontrolsonscreens.

StylesandThemesAndroidprovidesseveralwaystoalterthestyleofviewsinyourapplication.We’llfirstcoverusingmarkuptagsinstringsandthenhowtouseSpannablestochangespecificvisualattributesoftext.Butwhatifyouwanttocontrolhowthingslookusingacommonspecificationforseveralviewsoracrossanentireactivityorapplication?We’lldiscussAndroidstylesandthemestoshowyouhow.

UsingStylesSometimes,youwanttohighlightorstyleaportionoftheView’scontent.Youcandothisstaticallyordynamically.Statically,youcanapplymarkupdirectlytothestringsinyourstringresources,asshownhere:

<stringname="styledText"><i>Static</i>styleina<b>TextView</b>.</string>

YoucanthenreferenceitinyourXMLorfromcode.NotethatyoucanusethefollowingHTMLtagswithstringresources:<i>,<b>,and<u>foritalics,bold,andunderlined,respectively,aswellas<sup>(superscript),<sub>(subscript),<strike>(strikethrough),<big>,<small>,and<monospace>.Youcanevennestthesetoget,forexample,smallsuperscripts.ThisworksnotjustinTextViewsbutalsoinotherviews,likebuttons.Figure5-2showswhatstyledandthemedtextlookslike,usingmanyoftheexamplesinthissection.

Figure5-2.Examplesofstylesandthemes

StylingaTextViewcontrol’scontentprogrammaticallyrequiresalittleadditionalworkbutallowsformuchmoreflexibility(seeListing5-2),becauseyoucanstyleitatruntime.ThisflexibilitycanonlybeappliedtoaSpannable,though,whichishowEditTextnormallymanagestheinternaltext,whereasTextViewdoesnotnormallyuseSpannable.SpannableisbasicallyaStringtowhichyoucanapplystyles.TogetaTextViewtostoretextasaSpannable,youcancallsetText()thisway:

tv.setText("ThistextisstoredinaSpannable",TextView.BufferType.SPANNABLE);

Then,whenyoucalltv.getText(),you’llgetaSpannable.

AsshowninListing5-2,youcangetthecontentoftheEditText(asaSpannableobject)andthensetstylesforportionsofthetext.Thecodeinthelistingsetsthetextstylingtoboldanditalicsandsetsthebackgroundtored.YoucanuseallthestylingoptionsaswehavewiththeHTMLtagsasdescribedpreviously,andthensome.

Listing5-2.ApplyingStylesDynamicallytotheContentofanEditText

EditTextet=(EditText)this.findViewById(R.id.et);et.setText("StylingthecontentofanEditTextdynamically");Spannablespn=(Spannable)et.getText();spn.setSpan(newBackgroundColorSpan(Color.RED),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);spn.setSpan(newStyleSpan(android.graphics.Typeface.BOLD_ITALIC),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Thesetwotechniquesforstylingworkonlyontheoneviewthey’reappliedto.Androidprovidesastylemechanismtodefineacommonstyletobereusedacrossviews,aswellasathememechanism,whichbasicallyappliesastyletoanentireactivityortheentireapplication.Tobeginwith,weneedtotalkaboutstyles.

AstyleisacollectionofViewattributesthatisgivenanamesoyoucanrefertothatcollectionbyitsnameandassignthatstylebynametoviews.Forexample,Listing5-3showsaresourceXMLfile,savedin/res/values,thatwecoulduseforallerrormessages.

Listing5-3.DefiningaStyletoBeUsedAcrossManyViews

<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText"><itemname="android:layout_width">fill_parent</item><itemname="android:layout_height">wrap_content</item><itemname="android:textColor">#FF0000</item><itemname="android:typeface">monospace</item></style></resources>

Thesizeoftheviewisdefinedaswellasthefontcolor(red)andtypeface.NoticehowthenameattributeoftheitemtagistheXMLattributenameweusedinourlayoutXMLfiles,andthevalueoftheitemtagnolongerrequiresdoublequotes.WecannowusethisstyleforanerrorTextView,asshowninListing5-4.

Listing5-4.UsingaStyleinaView

<TextViewandroid:id="@+id/errorText"style="@style/ErrorText"android:text="Noerrorsatthistime"/>

ItisimportanttonotethattheattributenameforastyleinthisViewdefinitiondoesnotstartwithandroid:.Watchoutforthis,becauseeverythingseemstouseandroid:exceptthestyle.Whenyou’vegotmanyviewsinyourapplicationthatshareastyle,changingthatstyleinoneplaceismuchsimpler;youneedtomodifythestyle’sattributesonlyintheoneresourcefile.Youcan,ofcourse,createmanydifferentstylesforvarious

controls.Buttonscouldshareacommonstyle,forexample,that’sdifferentfromthecommonstylefortextinmenus.

Onereallyniceaspectofstylesisthatyoucansetupahierarchyofthem.WecoulddefineanewstyleforreallybaderrormessagesandbaseitonthestyleofErrorText.Listing5-5showshowthismightlook.

Listing5-5.DefiningaStylefromaParentStyle

<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText.Danger"><itemname="android:textStyle">bold</item></style></resources>

Thisexampleshowsthatwecansimplynameourchildstyleusingtheparentstyleasaprefixtothenewstylename.Therefore,ErrorText.DangerisachildofErrorTextandinheritsthestyleattributesoftheparent.ItthenaddsanewattributefortextStyle.Thiscanberepeatedagainandagaintocreateawholetreeofstyles.

Aswasthecaseforadapterlayouts,Androidprovidesalargesetofstylesthatwecanuse.TospecifyanAndroid-providedstyle,usesyntaxlikethis:

style="@android:style/TextAppearance"

ThisstylesetsthedefaultstylefortextinAndroid.TolocatethemasterAndroidstyles.xmlfile,visittheAndroidSDK/platforms/<android-version>/data/res/values/folder.Insidethisfile,youwillfindquiteafewstylesthatareready-madeforyoutouseorextend.Here’sawordofcautionaboutextendingtheAndroid-providedstyles:thepreviousmethodofusingaprefixwon’tworkwithAndroid-providedstyles.Instead,youmustusetheparentattributeofthestyletag,likethis:

<stylename="CustomTextAppearance"parent="@android:style/TextAppearance"><item...yourextensionsgohere…/></style>

Youdon’talwayshavetopullinanentirestyleonyourview.Youcouldchoosetoborrowjustapartofthestyleinstead.Forexample,ifyouwanttosetthecolorofthetextinyourTextViewtoasystemstylecolor,youcoulddothefollowing:

<EditTextandroid:id="@+id/et2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textColor="?android:textColorSecondary"android:text="@string/hello_world"/>

Noticethatinthisexample,thenameofthetextColorattributevaluestartswiththe?

characterinsteadofthe@character.The?characterisusedsoAndroidknowstolookforastylevalueinthecurrenttheme.Becausewesee?android,welookintheAndroidsystemthemeforthisstylevalue.

UsingThemesOneproblemwithstylesisthatyouneedtoaddanattributespecificationofstyle=”@style/…”toeveryviewdefinitionthatyouwantittoapplyto.Ifyouhavesomestyleelementsyouwantappliedacrossanentireactivity,oracrossthewholeapplication,youshoulduseathemeinstead.Athemeisreallyjustastyleappliedbroadly;butintermsofdefiningatheme,it’sexactlylikeastyle.Infact,themesandstylesarefairlyinterchangeable:youcanextendathemeintoastyleorrefertoastyleasatheme.Typically,onlythenamesgiveahintastowhetherastyleisintendedtobeusedasastyleoratheme.

Tospecifyathemeforanactivityoranapplication,addanattributetothe<activity>or<application>tagintheAndroidManifest.xmlfileforyourproject.Thecodemightlooklikethis:

<activityandroid:theme="@style/MyActivityTheme"><applicationandroid:theme="@style/MyApplicationTheme"><applicationandroid:theme="@android:style/Theme.NoTitleBar">

YoucanfindtheAndroid-providedthemesinthesamefolderastheAndroid-providedstyles,withthethemesinafilecalledthemes.xml.Whenyoulookinsidethethemesfile,youwillseealargesetofstylesdefined,withnamesthatstartwithTheme.YouwillalsonoticethatwithintheAndroid-providedthemesandstyles,thereisalotofextendinggoingon,whichiswhyyouendupwithstylescalledTheme.Dialog.AppError,forexample.

ThisconcludesourdiscussionoftheAndroidcontrolset.Aswementionedinthebeginningofthechapter,buildingUIsinAndroidrequiresyoutomastertwothings:thecontrolsetandthelayoutmanagers.Inthenextsection,wearegoingtodiscusstheAndroidlayoutmanagers.

UnderstandingLayoutManagersAndroidoffersacollectionofviewclassesthatactascontainersforviews.Thesecontainerclassesarecalledlayouts(orlayoutmanagers),andeachimplementsaspecificstrategytomanagethesizeandpositionofitschildren.Forexample,theLinearLayoutclasslaysoutitschildreneitherhorizontallyorvertically,oneaftertheother.AlllayoutmanagersderivefromtheViewclass,thereforeyoucannestlayoutmanagersinsideofoneanother.

ThelayoutmanagersthatshipwiththeAndroidSDKincludethecommonlyusedonesdefinedinTable5-1.

Table5-1.AndroidLayoutManagers

LayoutManager Description

LinearLayout Organizesitschildreneitherhorizontallyorvertically

TableLayout Organizesitschildrenintabularform

RelativeLayout Organizesitschildrenrelativetooneanotherortotheparent

FrameLayout Allowsyoutodynamicallychangethecontrol(s)inthelayout

GridLayout Organizesitschildreninagridarrangement

Wewilldiscusstheselayoutmanagersinthesectionsthatfollow.ThelayoutmanagercalledAbsoluteLayouthasbeendeprecatedandwillnotbecoveredinthisbook.

TheLinearLayoutLayoutManagerTheLinearLayoutlayoutmanageristhemostbasic.Thislayoutmanagerorganizesitschildreneitherhorizontallyorverticallybasedonthevalueoftheorientationproperty.We’veusedLinearLayoutinseveralofourexamplessofar.Listing5-6showsLinearLayoutwithahorizontalconfiguration.

Listing5-6.LinearLayoutwithaHorizontalConfiguration

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">

<!--addchildrenhere-->

</LinearLayout>

YoucancreateaverticallyorientedLinearLayoutbysettingthevalueoforientationtovertical.Becauselayoutmanagerscanbenested,youcould,forexample,constructaverticallayoutmanagerthatcontainedhorizontallayoutmanagerstocreateafill-inform,whereeachrowhadalabelnexttoanEditTextcontrol.Eachrowwouldbeitsownhorizontallayout,buttherowsasacollectionwouldbeorganizedvertically.

UnderstandingWeightandGravityTheorientationattributeisthefirstimportantattributerecognizedbytheLinearLayoutlayoutmanager.Otherimportantpropertiesthatcanaffectsizeandpositionofchildcontrolsareweightandgravity.

Youuseweighttoassignsizeimportancetoacontrolrelativetotheothercontrolsinthecontainer.Supposeacontainerhasthreecontrols:onehasaweightof1,whereastheothershaveaweightof0.Inthiscase,thecontrolwhoseweightequals1willconsumetheemptyspaceinthecontainer.Gravityisessentiallyalignment.Forexample,ifyouwanttoalignalabel’stexttotheright,youwouldsetitsgravitytoright.Therearequiteafewpossiblevaluesforgravity,includingleft,center,right,top,bottom,center_vertical,clip_horizontal,andothers.Seedeveloper.android.comfordetailsontheseandtheothervaluesofgravity.

NoteLayoutmanagersextendandroid.widget.ViewGroup,asdomanycontrol-basedcontainerclassessuchasListView.Althoughthelayoutmanagersandcontrol-basedcontainersextendthesameclass,thelayoutmanagerclasses,byconventionifnotstrictrequirement,dealwiththesizingandpositionofcontrolsandnotuserinteractionwithchildcontrols.

Nowlet’slookatanexampleinvolvingtheweightandgravityproperties(seeFigure5-3).

Figure5-3.UsingtheLinearLayoutlayoutmanager

Figure5-3showsthreeUIsthatutilizeLinearLayout,withdifferentweightandgravitysettings.TheUIontheleftusesthedefaultsettingsforweightandgravity.TheXMLlayoutforthisfirstUIisshowninListing5-7.

Listing5-7.ThreeTextFieldsArrangedVerticallyinaLinearLayout,UsingDefaultValuesforWeightandGravity

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<EditTextandroid:layout_width="fill_parent"

android:layout_height="wrap_content"android:text="one"/><EditTextandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="two"/><EditTextandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="three"/></LinearLayout>

TheUIinthecenterofFigure5-3usesthedefaultvalueforweightbutsetsandroid:gravityforthecontrolsinthecontainertoleft,center,andright,respectively.Thelastexamplesetstheandroid:layout_weightattributeofthecentercomponentto1.0andleavestheotherstothedefaultvalueof0.0(seeListing5-8).Bysettingtheweightattributeto1.0forthemiddlecomponentandleavingtheweightattributesfortheothertwocomponentsat0.0,wearespecifyingthatthecentercomponentshouldtakeupalltheremainingwhitespaceinthecontainerandthattheothertwocomponentsshouldremainattheiridealsize.

Similarly,ifyouwanttwoofthethreecontrolsinthecontainertosharetheremainingwhitespaceamongthem,youwouldsettheweightto1.0forthosetwoandleavethethirdoneat0.0.Finally,ifyouwantthethreecomponentstosharethespaceequally,you’dsetalloftheirweightvaluesto1.0.Doingthiswouldexpandeachtextfieldequally.

Listing5-8.LinearLayoutwithWeightConfigurations

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<EditTextandroid:layout_width="fill_parent"android:layout_weight="0.0"android:layout_height="wrap_content"android:text="one"android:gravity="left"/>

<EditTextandroid:layout_width="fill_parent"android:layout_weight="1.0"android:layout_height="wrap_content"android:text="two"android:gravity="center"/>

<EditTextandroid:layout_width="fill_parent"android:layout_weight="0.0"android:layout_height="wrap_content"android:text="three"android:gravity="right"/></LinearLayout>

android:gravityvs.android:layout_gravityNotethatAndroiddefinestwosimilargravityattributes:android:gravityandandroid:layout_gravity.Here’sthedifference:android:gravityisasettingusedbytheview,whereasandroid:layout_gravityisusedbythecontainer(android.view.ViewGroup).Forexample,youcansetandroid:gravitytocentertohavethetextintheEditTextcenteredwithinthecontrol.Similarly,youcanalignanEditTexttothefarrightofaLinearLayout(thecontainer)bysettingandroid:layout_gravity=“right”.SeeFigure5-4andListing5-9.

Figure5-4.Applyinggravitysettings

Listing5-9.UnderstandingtheDifferenceBetweenandroid:gravityandandroid:layout_gravity

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<EditTextandroid:layout_width="wrap_content"android:gravity="center"android:layout_height="wrap_content"android:text="one"android:layout_gravity="right"/></LinearLayout>

AsshowninFigure5-4,thetextiscenteredintheEditText,whichisalignedtotherightoftheLinearLayout.

TheTableLayoutLayoutManagerTheTableLayoutlayoutmanagerisanextensionofLinearLayout.Thislayoutmanagerstructuresitschildcontrolsintorowsandcolumns.Listing5-10showsanexample.

Listing5-10.ASimpleTableLayout

<?xmlversion="1.0"encoding="utf-8"?><TableLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent">

<TableRow><TextViewandroid:text="FirstName:"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<EditTextandroid:text="Edgar"android:layout_width="wrap_content"android:layout_height="wrap_content"/></TableRow>

<TableRow><TextViewandroid:text="LastName:"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<EditTextandroid:text="Poe"android:layout_width="wrap_content"android:layout_height="wrap_content"/></TableRow>

</TableLayout>

Tousethislayoutmanager,youcreateaninstanceofTableLayoutandplaceTableRowelementswithinit.TheseTableRowelementscontainthecontrolsofthetable.TheUIforListing5-10isshowninFigure5-5.

Figure5-5.TheTableLayoutlayoutmanager

ThereareanumberofmorecomplexlayoutspossiblewithTableLayout,includingnesting,asymmetricalrowsandcolumns,andmore.WehaveabonussectiononmoreoptionsforTableLayoutonthebookwebsite,www.androidbook.com.

TheRelativeLayoutLayoutManagerAnotherinterestinglayoutmanagerisRelativeLayout.Asthenamesuggests,thislayoutmanagerimplementsapolicywherethecontrolsinthecontainerarelaidoutrelativetoeitherthecontaineroranothercontrolinthecontainer.Listing5-11andFigure5-6showanexample.

Listing5-11.UsingaRelativeLayoutLayoutManager

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content">

<TextViewandroid:id="@+id/userNameLbl"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Username:"android:layout_alignParentTop="true"/>

<EditTextandroid:id="@+id/userNameText"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_toRightOf="@id/userNameLbl"/>

<TextViewandroid:id="@+id/pwdLbl"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/userNameText"android:text="Password:"/>

<EditTextandroid:id="@+id/pwdText"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_toRightOf="@id/pwdLbl"android:layout_below="@id/userNameText"/>

<TextViewandroid:id="@+id/pwdCriteria"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_below="@id/pwdText"android:text="PasswordCriteria…"/>

<TextViewandroid:id="@+id/disclaimerLbl"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:text="Useatyourownrisk…"/>

</RelativeLayout>

Figure5-6.AUIlaidoutusingtheRelativeLayoutlayoutmanager

Asshown,theUIlookslikeasimpleloginform.Theusernamelabelispinnedtothetopofthecontainer,becausewesetandroid:layout_alignParentToptotrue.Similarly,theUsernameinputfieldispositionedbelowtheUsernamelabelbecausewesetandroid:layout_below.ThePasswordlabelappearsbelowtheUsernamelabel,andthePasswordinputfieldappearsbelowthePasswordlabel.Thedisclaimerlabelispinnedtothebottomofthecontainerbecausewesetandroid:layout_alignParentBottomtotrue.

Besidesthesethreelayoutattributes,youcanalsospecifylayout_above,layout_toRightOf,layout_toLeftOf,layout_centerInParent,andseveralmore.WorkingwithRelativeLayoutisfunduetoitssimplicity.Infact,onceyoustartusingit,it’llbecomeyourfavoritelayoutmanager—you’llfindyourselfgoingbacktoitoverandoveragain.

TheFrameLayoutLayoutManagerThelayoutmanagersthatwe’vediscussedsofarimplementvariouslayoutstrategies.Inotherwords,eachonehasaspecificwaythatitpositionsandorientsitschildrenonthescreen.Withtheselayoutmanagers,youcanhavemanycontrolsonthescreenatonetime,eachtakingupaportionofthescreen.Androidalsooffersalayoutmanagerthatismainlyusedtodisplayasingleitem:FrameLayout.Youmainlyusethisutilitylayoutclasstodynamicallydisplayasingleview,butyoucanpopulateitwithmanyitems,settingonetovisiblewhiletheothersareinvisible.Listing5-12demonstratesusingFrameLayout.

Listing5-12.PopulatingFrameLayout

<?xmlversion="1.0"encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/frmLayout"android:layout_width="fill_parent"android:layout_height="fill_parent">

<ImageView

android:id="@+id/oneImgView"android:src="@drawable/one"android:scaleType="fitCenter"android:layout_width="fill_parent"android:layout_height="fill_parent"/><ImageViewandroid:id="@+id/twoImgView"android:src="@drawable/two"android:scaleType="fitCenter"android:layout_width="fill_parent"android:layout_height="fill_parent"android:visibility="gone"/>

</FrameLayout>

publicclassFrameLayoutActivityextendsActivity{privateImageViewone=null;privateImageViewtwo=null;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.listing6_48);

one=(ImageView)findViewById(R.id.oneImgView);two=(ImageView)findViewById(R.id.twoImgView);

one.setOnClickListener(newOnClickListener(){

publicvoidonClick(Viewview){two.setVisibility(View.VISIBLE);

view.setVisibility(View.GONE);}});

two.setOnClickListener(newOnClickListener(){

publicvoidonClick(Viewview){one.setVisibility(View.VISIBLE);

view.setVisibility(View.GONE);}});}}

Listing5-12showsthelayoutfileaswellastheonCreate()methodoftheactivity.TheideaofthedemonstrationistoloadtwoImageViewobjectsintheFrameLayout,withonlyoneoftheImageViewobjectsvisibleatatime.IntheUI,whentheuserclicksthevisibleimage,wehideoneimageandshowtheotherone.

LookatListing5-12morecloselynow,startingwiththelayout.Youcanseethatwe

defineaFrameLayoutwithtwoImageViewobjects(anImageViewisacontrolthatknowshowtodisplayimages).NoticethatthesecondImageView’svisibilityissettogone,makingthecontrolinvisible.Now,lookattheonCreate()method.IntheonCreate()method,weregisterlistenerstoclickeventsontheImageViewobjects.Intheclickhandler,wehideoneImageViewandshowtheother.

Aswesaidearlier,yougenerallyuseFrameLayoutwhenyouneedtodynamicallysetthecontentofaviewtoasinglecontrol.Althoughthisisthegeneralpractice,thecontrolwillacceptmanychildren,aswedemonstrated.Listing5-12addstwocontrolstothelayoutbuthasoneofthecontrolsvisibleatatime.FrameLayout,however,doesnotforceyoutohaveonlyonecontrolvisibleatatime.Ifyouaddmanycontrolstothelayout,FrameLayoutwillsimplystackthecontrols,oneontopoftheother,withthelastoneontop.ThiscancreateaninterestingUI.Forexample,Figure5-7showsaFrameLayoutcontrolwithtwoImageViewobjectsthatarevisible.Youcanseethatthecontrolsarestacked,andthatthetoponeispartiallycoveringtheimagebehindit.

Figure5-7.FrameLayoutwithtwoImageViewobjects

AnotherinterestingaspectoftheFrameLayoutisthatifyouaddmorethanonecontroltothelayout,thesizeofthelayoutiscomputedasthesizeofthelargestiteminthecontainer.InFigure5-7,thetopimageisactuallymuchsmallerthantheimagebehindit,

butbecausethesizeofthelayoutiscomputedbasedonthelargestcontrol,theimageontopisstretched.AlsonotethatifyouputmanycontrolsinsideaFrameLayoutwithoneormoreoftheminvisibletostart,youmightwanttoconsiderusingsetMeasureAllChildren(true)onyourFrameLayout.Becausethelargestchilddictatesthelayoutsize,you’llhaveaproblemifthelargestchildisinvisibletobeginwith:whenitbecomesvisible,itisonlypartiallyvisible.Toensurethatallitemsarerenderedproperly,callsetMeasureAllChildren()andpassitavalueoftrue.TheequivalentXMLattributeforFrameLayoutisandroid:measureAllChildren=“true”.

TheGridLayoutLayoutManagerAndroid4.0broughtwithitanewlayoutmanagercalledGridLayout.Asyoumightexpect,itlaysoutviewsinagridpatternofrowsandcolumns,somewhatlikeTableLayout.However,it’seasiertousethanTableLayout.WithaGridLayout,youcanspecifyarowandcolumnvalueforaview,andthat’swhereitgoesinthegrid.Thismeansyoudon’tneedtospecifyaviewforeverycell,justthosethatyouwanttoholdaview.Viewscanspanmultiplegridcells.Youcanevenputmorethanoneviewintothesamegridcell.

Whenlayingoutviews,youmustnotusetheweightattribute,becauseitdoesnotworkinchildviewsofaGridLayout.Youcanusethelayout_gravityattributeinstead.OtherinterestingattributesyoucanusewithGridLayoutchildviewsincludelayout_columnandlayout_columnSpantospecifytheleft-mostcolumnandthenumberofcolumnstheviewtakesup,respectively.Similarly,therearelayout_rowandlayout_rowSpanattributes.Interestingly,youdonotneedtospecifylayout_heightandlayout_widthforGridLayoutchildviews;theydefaulttoWRAP_CONTENT.

CustomizingtheLayoutforVariousDeviceConfigurationsBynow,youknowverywellthatAndroidoffersahostoflayoutmanagersthathelpyoubuildUIs.Ifyou’veplayedaroundwiththelayoutmanagerswe’vediscussed,youknowthatyoucancombinethelayoutmanagersinvariouswaystoobtainthelookandfeelyouwant.Butevenwithallthelayoutmanagers,buildingUIs—andgettingthemright—canbeachallenge.Thisisespeciallytrueformobiledevices.Usersandmanufacturersofmobiledevicesaregettingmoreandmoresophisticated,andthatmakesthedeveloper’sjobevenmorechallenging.

OneofthechallengesisbuildingaUIforanapplicationthatdisplaysinvariousscreenconfigurations.Forexample,whatwouldyourUIlooklikeifyourapplicationweredisplayedinportraitversuslandscapemode?Ifyouhaven’trunintothisyet,yourmindisprobablyracingrightnow,wonderinghowtodealwiththiscommonscenario.

Interestingly,andfortunately,Androidprovidessomesupportforthisusecase.

Here’showitworks:whenbuildingalayout,Androidwillfindandloadlayoutsfromspecificfoldersbasedontheconfigurationofthedevice.Adevicecanbeinoneofthreeconfigurations:portrait,landscape,orsquare(squareisrare).Toprovidedifferentlayoutsforthevariousconfigurations,youhavetocreatespecificfoldersforeachconfigurationfromwhichAndroidwillloadtheappropriatelayout.Asyouknow,thedefaultlayoutfolderislocatedatres/layout.Tosupportportraitdisplay,createafoldercalledres/layout-port.Forlandscape,createafoldercalledres/layout-land.Andforasquare,createonecalledres/layout-square.

Agoodquestionatthispointis,“Withthesethreefolders,doIneedthedefaultlayoutfolder(res/layout)?”Generally,yes.Android’sresource-resolutionlogiclooksintheconfiguration-specificdirectoryfirst.IfAndroiddoesn’tfindaresourcethere,itgoestothedefaultlayoutdirectory.Therefore,youshouldplacedefaultlayoutdefinitionsinres/layoutandthecustomizedversionsintheconfiguration-specificfolders.

Anothertrickistousethe<include/>taginalayoutfile.Thisallowsyoutocreatecommonchunksoflayoutcode(forexample,inthedefaultlayoutdirectory)andincludetheminlayoutsdefinedinlayout-portandlayout-land.Anincludetagmightlooklikethis:

<includelayout="@layout/common_chunk1"/>

Iftheconceptofincludeinterestsyou,youshouldalsocheckoutthe<merge/>tagandtheViewStubclassintheAndroidAPI.Thesegiveyouevenmoreflexibilitywhenorganizinglayouts,withoutduplicatingviews.

NotethattheAndroidSDKdoesnotofferanyAPIsforyoutoprogrammaticallyspecifywhichconfigurationtoload—thesystemsimplyselectsthefolderbasedontheconfigurationofthedevice.Youcan,however,settheorientationofthedeviceincode,forexample,usingthefollowing:

importandroid.content.pm.ActivityInfo;...setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

Thisforcesyourapplicationtoappearonthedeviceinlandscapemode.Goaheadandtryitinoneofyourearlierprojects.AddthecodetoyouronCreate()methodofanactivity,runitintheemulator,andseeyourapplicationsideways.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutbuildinguserinterfaces:

Themaintypesoflayoutsandwhentouseeach

ViewssupportedinAndroidandhowtodefinethembothinXML

andviacode

Stylesandthemesyoucanusetomanagethelookandfeelofyourapplicationfromacommonsetofresources

Chapter6

WorkingwithMenusandActionBarsAndroidSDKsupportsregularmenus,submenus,contextmenus,iconmenus,andsecondarymenus.Android3.0introducedtheactionbar,whichintegrateswellwithmenus.Wewillcoverbothmenusandactionbarsinthischapter.

Likemanyotherchaptersinthebook,wewillpresenttheessentialcodesnippetsthatyoucanusetoworkwithmenusandactionbars.Thecompletercodecontextforthesesnippetsisavailableinthedownloadableapplicationthatisspecificallydevelopedforthischapter.Thelinkforthesedownloadableprojectsisgiveninthe“Resources”sectionattheendofthischapter.

WorkingwithMenusThroughXMLFilesInAndroidtheeasiestwaytoworkwithmenusisthroughXMLmenuresourcefiles.ThisXMLapproachtomenucreationoffersseveraladvantages,suchastheabilitytonamemenus,orderthemautomatically,andallocateIDs.AsXMLmenusareresources,youalsogetthelocalizationsupportforthemenutextandicons.

CreatingXMLMenuResourceFilesAsamplemenuXMLfileisgivenListing6-1.YouseeinthislistingaseriesofmenuitemsgroupedtogetherunderagroupXMLnode.YoucanspecifyanIDforthegroupusingthe@+idresourcereferenceapproach.YoucanusethisIDinjavacodetogetaccesstothemenugroupandmanageitwhenneeded.GroupingisoptionalandyoucanomitthegroupXMLnode.

EachmenuXMLfilehasaseriesofmenuitemswiththeirmenuitemIDstiedtosymbolicnames.Thetitleindicatesthemenutitle,andtheorderInCategoryindicatestheorderinwhichthemenuitemappearsinthemenu.YoucanrefertotheAndroidSDKdocumentationforallthepossibleattributesfortheseXMLtags.ThereferenceURLisprovidedinthe“Resources”sectionofthischapter.

Listing6-1.MenuXMLResourceFilewithMenuDefinitions

<menuxmlns:android="http://schemas.android.com/apk/res/android"><groupandroid:id="@+id/menuGroup_Main">

<itemandroid:id="@+id/menu_item1"

android:orderInCategory="1"android:title="item1text"/><itemandroid:id="@+id/menu_item2"

android:orderInCategory="2"android:enabled="true"

android:icon="@drawable/some-file"android:title="item2text"/><itemandroid:id="@+id/menu_item3"

android:orderInCategory="3"android:title="item3text"/></group></menu>

AllthechildmenuitemsinListing6-1areallocatedmenuitemIDsbasedontheirnames(example:menu_item1)inthisXMLfile.Let’sseenowhowwetakethismenuXMLfileandassociateitwithanactivity.

PopulatingActivityMenufromMenuXMLFilesAssumethatthenameofthemenuXMLfileismy_menu.xml.Youneedtoplacethisfileinthe/res/menusubdirectory.Placingthefilein/res/menuautomaticallygeneratesaresourceIDcalledR.menu.my_menu.

ThekeyclassinAndroidmenusupportisandroid.view.Menu.EveryactivityinAndroidisassociatedwithonemenuobjectofthistype.InthelifecycleofanactivityAndroidcallsamethodcalledonCreateOptionsMenu()topopulatethisMenuobject.InthismethodweloadtheXMLmenufileintotheMenuobject.ThisisshowninListing6-2.

Listing6-2.UsingMenuInflater

//Thiscallbackmethodisavailableoneveryactivityclass@OverridepublicbooleanonCreateOptionsMenu(Menumenu){super.onCreateOptionsMenu(menu);MenuInflaterinflater=getMenuInflater();//fromactivityinflater.inflate(R.menu.my_menu,menu);

//Itisimportanttoreturntruetoseethemenureturntrue;

}

Oncethemenuitemsarepopulated,thecodeshouldreturntruetomakethemenuvisible.Ifthismethodreturnsfalse,themenuisinvisible.

RespondingtoXML-BasedMenuItemsYourespondtomenuitemsintheonOptionsItemSelected()callbackmethod.AndroidnotonlygeneratesaresourceIDfortheXMLmenufile(asusedinListing6-2)butalsogeneratesthenecessarymenuitemIDstohelpyoudistinguishbetweenthemenuitems.ThecodeinListing6-3illustrateshowtorespondtomenuitems.

Listing6-3.RespondingtoMenuItemsfromanXMLMenuResourceFile

@OverridepublicvoidonOptionsItemSelected(MenuItemitem){if(item.getItemId()==R.id.menu_item1){

//dosomething//foritemshandledreturntrue;}elseif(item.getItemId()==R.id.menu_item2){

//dosomethingreturntrue;}//fortherest...returnsuper.onOptionsItemSelected(item);}

NoticehowthemenuitemnamesfromtheXMLmenuresourcefilehaveautomaticallygeneratedmenuitemIDsintheR.idspace.

StartinginSDK3.0,youcanalsousetheandroid:onClickattributeofamenuitemtodirectlyindicatethenameofamethodinanactivitythatisattachedtothismenu.Thisactivitymethodisthencalledwiththemenuitemobjectasthesoleinput.Thisfeatureisonlyavailablein3.0andabove.Listing6-4showsanexample.

Listing6-4.SpecifyingaMenuCallbackMethodinanXMLMenuResourceFile

<itemandroid:id="..."android:onClick="a-method-name-in-your-activity"...</item>

ItisthissimpletoworkwithmenuitemsinAndroid.Let’sexploretheJavaAPIforthemenusabitnow.

WorkingwithMenusinJavaCodeAsindicatedthekeyclassinAndroidmenusupportisandroid.view.Menu.EveryactivityinAndroidisassociatedwithonemenuobjectofthistype.Themenuobjectthencontainsanumberofmenuitemsandsubmenus.Menuitemsarerepresentedbyandroid.view.MenuItem.Submenusarerepresentedbyandroid.view.SubMenu.

PriortoSDK3.0,onCreateOptionsMenu()iscalledthefirsttimeanactivity’soptionsmenuisaccessed.Startingwith3.0,thismethodiscalledaspartofactivitycreation.Alsonotethatthismethodiscalledonlyonceforthelifecycleoftheactivity.IfyouwanttoaddmenusdynamicallyyouwillneedtousethemethodonPrepareOptionsMenu(),whichiscoveredalittlelater.ThecodeinListing6-5

showshowtoaddthreemenuitemsusingasinglegroupIDalongwithincrementalmenuitemIDsandorderIDs.

Listing6-5.AddingMenuItems

@OverridepublicbooleanonCreateOptionsMenu(Menumenu){super.onCreateOptionsMenu(menu);menu.add(0//Group,1//itemid,0//order,"item1");//title

menu.add(0,2,1,"item2");menu.add(0,3,2,"item3");//Itisimportanttoreturntruetoseethemenureturntrue;}

Youshouldalsocallthebase-classimplementationofthismethodtogivethesystemanopportunitytopopulatethemenuwithsystemmenuitems(nosystemmenuitemsaredefinedsofar).

TheargumentstocreatethemenuitemareexplainedinListing6-5.Thelastargumentisthenameortitleofthemenuitem.Insteadoffreetext,youcanuseastringresourcethroughtheR.javaconstantsfile.Thegroup,menuitem,andorderIDsarealloptional;youcanuseMenu.NONEifyoudon’twanttospecifyanyofthem.IfMenu.NONEisspecifiedforagroup,thentheitemsareoutsideofanygroup.IfMenu.NONEisspecifiedforanitem,thenthismightbeasubmenuoraseparator.IfMenu.NONEisspecifiedfortheorder,Androidwillchoosesomemechanismtoorderthem.

WorkingwithMenuGroupsNow,let’slookathowtoworkwithmenugroups.Listing6-6showshowyouaddtwogroupsofmenus:Group1andGroup2.

Listing6-6.UsingGroupIDstoCreateMenuGroups

@OverridepublicbooleanonCreateOptionsMenu(Menumenu){//Group1intgroup1=1;menu.add(group1,1,1,"g1.item1");menu.add(group1,2,2,"g1.item2");

//Group2intgroup2=2;menu.add(group2,3,3,"g2.item1");menu.add(group2,4,4,"g2.item2");

returntrue;//itisimportanttoreturntrue}

Androidprovidesasetofmethodsontheandroid.view.MenuclassthatarebasedongroupIDs.Youcanmanipulateagroup’smenuitemsusingthemethodsshowninListing6-7:

Listing6-7.MenuGroup–RelatedMethods

removeGroup(id)setGroupCheckable(id,checkable,exclusive)setGroupEnabled(id,enabled)setGroupVisible(id,visible)

removeGroup()removesallmenuitemsfromthatgroup,giventhegroupID.YoucanenableordisablemenuitemsinagivengroupusingthesetGroupEnabledmethod().Similarly,youcancontrolthevisibilityofagroupofmenuitemsusingsetGroupVisible().

setGroupCheckable()isinteresting.Youcanusethismethodtoshowacheckmarkonamenuitemwhenthatmenuitemisselected.Whenappliedtoagroup,itenablesthisfunctionalityforallmenuitemswithinthatgroup.Ifthismethod’sexclusiveflagisset,onlyonemenuitemwithinthatgroupisallowedtogointoacheckedstate.Theothermenuitemsremainunchecked.

Younowknowhowtopopulateanactivity’smainmenuwithasetofmenuitemsandgroupthemaccordingtotheirnature.ThewayyourespondtothesemenuitemsisidenticaltohowyouwouldhaverespondedtofortheirXMLcounterpartsexceptthatthemenuitemsIDsareexplicitlycontrolledbytheprogrammer.

RespondingtoMenuItemsThroughListenersYouusuallyrespondtomenusbyoverridingonOptionsItemSelected();amenuitemalsoallowsyoutoregisteralistenerthatcouldbeusedasacallback.Thisapproachisatwo-stepprocess.Inthefirststep,youimplementtheMenuItem.OnMenuItemClickListenerinterface.Then,youtakeaninstanceofthisimplementationandpassittothemenuitem.Whenthemenuitemisclicked,themenuitemcallstheonMenuItemClick()methodoftheMenuItem.OnMenuItemClickListenerinterface(seeListing6-8).

Listing6-8.UsingaListenerasaCallbackforaMenuItemClick

//Step1publicclassMyResponseimplementsMenuItem.OnMenuItemClickListener{publicMyResponse(...someargs…){}//aconstructor@overridepublicbooleanOnMenuItemClick(MenuItemitem){

//doyourthingreturntrue;}}

//Step2MyResponsemyResponse=newMyResponse(..yourargs..);//supplyyourargsmenuItem.setOnMenuItemClickListener(myResponse);...

TheonMenuItemClick()methodiscalledwhenthemenuitemhasbeeninvoked.Thiscodeexecutesassoonasthemenuitemisclicked,evenbeforetheonOptionsItemSelected()methodiscalled.IfonMenuItemClick()returnstrue,noothercallbacksareexecuted—includingtheonOptionsItemSelected()callbackmethod.ThismeansthatthelistenercodetakesprecedenceovertheonOptionsItemSelected()method.

UsinganIntenttoRespondtoMenuItemsYoucanalsoassociateamenuitemwithanintentbyusingtheMenuItem’smethodsetIntent(intent).Whenanintentisassociatedwithamenuitem,andnothingelsehandlesthemenuitem,thenthedefaultbehavioristoinvoketheintentusingstartActivity(intent).Forthistowork,allthehandlers—especiallytheonOptionsItemSelected()method—shouldcalltheparentclass’sonOptionsItemSelected()methodforthosemenuitemsthatarenothandled.

UnderstandingExpandedMenusIfanapplicationhasmoremenuitemsthanitcandisplayonthemainscreen,AndroidshowsaMoremenuitemtoallowtheusertoseetherest.Thismenu,calledanexpandedmenu,appearsautomaticallywhentherearetoomanymenuitemstodisplayinthelimitedamountofspace.

WorkingwithIconMenusAndroidsupportsnotonlytextbutalsoimagesoriconsaspartofitsmenurepertoire.Creatinganiconmenuitemisstraightforward.Youcreatearegulartext-basedmenuitemasbefore,andthenyouusethesetIcon()methodontheMenuItemclasstosettheimage.Youneedtousetheimage’sresourceID,soyoumustgenerateitfirstbyplacingtheimageoriconinthe/res/drawabledirectory.Forexample,iftheicon’sfilenameisballoons,thentheresourceIDisR.drawable.balloons.Listing6-9demonstrateshowtoaddanicontoamenuitem.

Listing6-9.AttachinganIcontoaMenuItem

//addamenuitemandrememberitsothatyoucanuseit//subsequentlytosettheicononit.MenuItemitem=menu.add(...);//supplythemenuitemdetailsitem.setIcon(R.drawable.balloons);

Theiconshowsaslongasthemenuitemisdisplayedonthemainapplicationscreen.Ifit’sdisplayedaspartoftheexpandedmenu,theicondoesn’tshow,justthetext.ThereisanicontagavailableaswelltoindicatetheiconinanXMLmenuresourcefile.ThereareconditionsunderwhichAndroidmaychoosenottoshowtheiconsandrecommendsthattextisalwaysprovided.

WorkingwithSubmenusAMenuobjectcanhavemultipleSubMenuobjects.EachSubMenuobjectisaddedtotheMenuobjectthroughacalltotheMenu.addSubMenu()method(seeListing6-10).Youaddmenuitemstoasubmenuthesamewaythatyouaddmenuitemstoamenu.ThisisbecauseSubMenuisalsoderivedfromaMenuobject.However,youcannotaddadditionalsubmenustoasubmenu.

Listing6-10.AddingSubmenus

privatevoidaddSubMenu(Menumenu){//Secondaryitemsareshownjustlikeeverythingelseintbase=Menu.FIRST+100;SubMenusm=menu.addSubMenu(base,base+1,Menu.NONE,"submenu");sm.add(base,base+2,base+2,"subitem1");sm.add(base,base+3,base+3,"subitem2");sm.add(base,base+4,base+4,"subitem3");

//thefollowingisoksm.setIcon(R.drawable.icon48x48_1);

//Thiswillresultinruntimeexception//sm.addSubMenu("trythis");}

NoteSubMenu,asasubclassoftheMenuobject,continuestocarrytheaddSubMenu()method.Thecompilerwon’tcomplainifyouaddasubmenutoanothersubmenu,butyou’llgetaruntimeexceptionifyoutrytodoit.

TheAndroidSDKdocumentationalsosuggeststhatsubmenusdonotsupporticonmenuitems.Whenyouaddanicontoamenuitemandthenaddthatmenuitemtoasubmenu,themenuitemignoresthaticon,evenifyoudon’tseeacompile-timeorruntimeerror.However,thesubmenuitselfcanhaveanicon.

WorkingwithContextMenusAndroidsupportstheideaofcontextmenusthroughanactioncalledalongclick.AlongclickisalongpresshelddownslightlylongerthanusualonanyAndroidview.Anactivityownsaregularoptionsmenu,whereasaviewownsacontextmenu.Thisistobeexpected,becausethelongclicksthatactivatecontextmenusapplytotheviewbeingclicked.Soanactivitycanhaveonlyoneoptionsmenubutmanycontextmenus.

RegisteringaViewforaContextMenuThefirststepinimplementingacontextmenuistoregisteraviewforthecontextmenuinanactivity’sonCreate()method.YoucanregisteraTextViewforacontextmenubyusingthecodeinListing6-11.YoufirstfindtheTextViewandthencallregisterForContextMenu()ontheactivityusingtheTextViewasanargument.ThissetsuptheTextViewforcontextmenus.

Listing6-11.RegisteringaTextViewforaContextMenu

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

TextViewtv=(TextView)this.findViewById(R.id.textViewId);registerForContextMenu(tv);}

PopulatingaContextMenuOnceaviewliketheTextViewinthisexampleisregisteredforcontextmenus,AndroidcallstheonCreateContextMenu()methodwiththisviewastheargument.Thisiswhereyoucanpopulatethecontextmenuitemsforthatcontextmenu.TheonCreateContextMenu()callbackmethodprovidesthreeargumentstoworkwith.Listing6-12demonstratestheonCreateContextMenu()method.

Listing6-12.TheonCreateContextMenu()Method

@OverridepublicvoidonCreateContextMenu(ContextMenumenu,Viewv,ContextMenuInfomenuInfo){menu.setHeaderTitle("SampleContextMenu");menu.add(200,200,200,"item1");}

ThefirstargumentisapreconstructedContextMenuobject,thesecondistheview

(suchastheTextView)thatgeneratedthecallback,andthethirdistheContextMenuInfoclass.Foralotofsimplecases,youcanjustignoretheContextMenuInfoobject.However,someviewsmaypassextrainformationthroughthisobject.Inthosecases,youneedtocasttheContextMenuInfoclasstoasubclassandthenusetheadditionalmethodstoretrievetheadditionalinformation.

SomeexamplesofclassesderivedfromContextMenuInfoincludeAdapterContextMenuInfoandExpandableListContextMenuInfo.ViewsthatareAdapterViewssuchastheListViewinAndroidusetheAdapterContextMenuInfoclasstopasstherowIDwithinthatviewforwhichthecontextmenuisbeingdisplayed.Inasense,youcanusethisclasstofurtherclarifytheobjectunderneaththetouchortheclick,evenwithinagivencompositeview.

RespondingtoContextMenuItemsAndroidprovidesacallbackmethodsimilartoonOptionsItemSelected()calledonContextItemSelected().Listing6-13demonstratesonContextItemSelected().

Listing6-13.RespondingtoContextMenus

//Thismethodisavailableforallactivities@OverridepublicbooleanonContextItemSelected(MenuItemitem){if(item.getItemId()==some-menu-item-id){//handlethismenuitemreturntrue;}...otherexceptionprocessing}

IncorporatingDynamicMenusSofar,we’vetalkedaboutstaticmenus—yousetthemuponce,andtheydon’tchangedynamicallyaccordingtowhat’sonscreen.Ifyouwanttocreatedynamicmenus,usetheonPrepareOptionsMenu()methodthatAndroidprovidesonanactivityclass.ThismethodresemblesonCreateOptionsMenu()exceptthatitiscalledeverytimeamenuisdisplayedpriortodisplaying.YoushoulduseonPrepareOptionsMenu()alongwiththeonCreateOptionsMenu()toeffectivelymanageyourmenuifithasdynamicmenuoptions.onPrepareOptionMenu()iswhereyouwanttoenableordisablesomemenuitemsormenugroupsbasedonwhatyouaredisplaying.For3.0andabovewhenyouwanttochangeamenu,becauseamenu-relatedcomponentliketheactionbarisalwaysdisplayed,youhavetoexplicitlycallanewprovisionedmethodcalledActivity.invalidateOptionsMenu(),whichinturninvokestheonCreateOptionsMenu()andredrawsthemenuandtherebyalsoresultsincallingonPrepareOptionsMenu()priortothedisplay.Youcancallthismethodanytime

somethingchangesinyourapplicationstatethatwouldrequireachangetothemenu.

WorkingwithPop-upMenusAndroid3.0introducedanothertypeofmenucalledapop-upmenu.SDK4.0enhancedthisslightlybyaddingacoupleofutilitymethods(forexample,PopupMenu.inflate)tothePopupMenuclass.(SeethePopupMenuAPIdocumentationtolearnaboutthesemethods.Listing6-14alsodrawsattentiontothisdifference.)

Apop-upmenucanbeinvokedagainstanyviewinresponsetoanyUIevent.AnexampleofaUIeventisabuttonclickoraclickonanimageview.Figure6-1showsapop-upmenuinvokedagainstaview.

Figure6-1.Pop-upmenuattachedtoatextview

Tocreateapop-upmenuliketheoneinFigure6-1,startwitharegularXMLmenufileandusetheJavacodeinListing6-14toloadthismenuXMLasapop-upmenu.Seethedownloadableprojectforthischapterifyouwanttoseethefullimplementation.

Listing6-14.WorkingwithaPop-upMenu

//Otheractivitycodegoeshere…//InvokethefollowingmethodtoshowapopupmenuprivatevoidshowPopupMenu(){//GetholdofaviewtoanchorthepopupTextViewtv=findViewById(R.id.SOME_TEXT_VIEW_ID);

//instantiateapopupmenu.var"this"standsforactivityPopupMenupopup=newPopupMenu(this,tv);

//thefollowingcodefor3.0sdk//popup.getMenuInflater().inflate(R.menu.popup_menu,popup.getMenu());//Orinsdk4.0andabovepopup.inflate(R.menu.popup_menu);popup.setOnMenuItemClickListener(newPopupMenu.OnMenuItemClickListener(){publicbooleanonMenuItemClick(MenuItemitem){//dosomethingherereturntrue;}});popup.show();}

Asyoucansee,apop-upmenubehavesmuchlikeanoptionsmenu.Thekeydifferencesareasfollows:

Apop-upmenuisusedondemand,whereasanoptionsmenuisalwaysavailable.

Apop-upmenuisanchoredtoaview,whereasanoptionsmenubelongstotheentireactivity.

Apop-upmenuusesitsownmenuitemcallback,whereastheoptionsmenuusestheonOptionsItemSelected()callbackontheactivity.

ExploringActionBarsIntroducedinAndroid3.0andexpandedinAndroid4.0,anActionBarextendsthereachofmenusintothetitlebarofanactivity.Thisallowsfrequentlyusedactionseasilyavailabletotheuserwithoutsearchingthroughoptionmenusorcontextmenus.Inadditiontoiconsandmenuitems,anactionbarcanaccommodateotherviewssuchastabs,oralist,orasearchboxtohelpwithnavigation.Figure6-2showsanactionbarintabbednavigationmode.

Figure6-2.Anactivitywithatabbedactionbar

Youcanseethevariouspartsofanactionbarhere.TheiconatupperleftontheactionbariscalledaHomeicon.ClickingthisHomeiconsendsacallbacktotheoptionmenuwithmenuIDandroid.R.id.home.Followedbythehomeiconisthetitleareaforthisactivity.Thenyouseeasetoftabs(oradrop-downlistifthisweretobealist-basedactionbar).Inthemiddle,youseesearchview.Towardstheend,youseeasetofactionicons.Thelastpartofthisactionbarisadottedverticallinerepresentingthemenuforthisactivity.Whenyouclickonthaticon,astandarddrop-downmenuwillappear(SeeFigure6-3).

TheactionbaryouseeinFigure6-1isatabbedactionbar.Thetwoothermodesofanactionbarareastandardandalist.Inalistactionbar,thetabsarereplacedbyadrop-downlist.Inastandardactionbar,thereisnoareasetasideforalistortabs.Now,let’sshowyouhowtoimplementasimplestandardactionbar.

ImplementingaStandardActionBarListing6-15presentssamplesourcecodeforimplementingastandardnavigationactionbarforanactivity.

Listing6-15.StandardNavigationActionBarActivity

publicclassStandardNavigationActionBarActivityextendsActivity{//.....othercode@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);

ActionBarbar=this.getActionBar();bar.setTitle("Sometitleofyourchoosing");bar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);}publicbooleanonCreateOptionsMenu(MenumainMenu){//loadthemenuxmlfileintothemainMenuobjectasusualherereturntrue;}}

AsyoucanseefromListing6-15,itiseasytoworkwithanactionbar.NoticeinthatlistinghowwehaveusedthegetActionBar()togetaccesstotheactionbarobjectandthensetitstitleandnavigationmode.AnymenuyousetintheonCreateOptionsMenu()canbeinvokeddirectlyfromtheactionbarasshowninFigure6-3.(However,whenamenuispresentedinthisfashionfromanactionbar,duetospacelimitations,thesystemmaynotshowtheiconsalongwithmenutext.)

Figure6-3.Anactivitywithanactionbarandexpandedmenu

Withtheintroductionofactionbar,themenuXMLfileisenhancedwithnewattributestoindicatesomemenuitemstobeshownintheactionbardirectlyasicons.(YoucanseetheseiconsinactionbarabovetheexpandedmenuinFigure6-3).TheXMLmenufileexampleinListing6-16demonstrateshowamenuitemcanbespecifiedtobecomeanicondirectlyontheactionbar.

Listing6-16.MenuXMLFileforThisProject

<!--/res/menu/menu.xml--><menuxmlns:android="http://schemas.android.com/apk/res/android"><!--Thisgroupusesthedefaultcategory.--><groupandroid:id="@+id/menuGroup_Main"><!--aregularmenuitem--><itemandroid:id="@+id/menu_da_clear"android:title="clear"/><!--itemtobeshowndirectlyontheactionbar--><itemandroid:id="@+id/menu_action_icon1"android:title="ActionIcon1"android:icon="@drawable/creep001"android:showAsAction="ifRoom"/>

<!--..othermenuitems--></group></menu>

ThemenuitemsthataretobeshownontheactionbarareindicatedwiththetagshowAsAction.Intheprecedingcode,thisattributeissetto“ifRoom“.TheotherpossiblevaluesforthisXMLtagareasfollows:always,never,withText,collapseActionView.YoucanalsoaccomplishthesameeffectwithaJavaAPIavailableontheMenuItemclass.Theoptionalwaysmeans“showthisitemasabuttonintheactionbar.”Theoptionnevermeans“nevershowthisitem.”TheoptionwithTextmeans“showthisitemwithitstextlabelandtheicon.”TheoptioncollapseActionViewmeans“collapsethespacetakenbytheactionviewofthisactionmenuitemwhennotselected.”Becausetheseactionsaremerelymenuitems,theybehaveassuchandcalltheonOptionsItemSelected()callbackmethodoftheactivityclass.

ImplementingaTabbedActionBarListing6-17showshowtosetupatabbedactionbar.

Listing6-17.Tab-Navigation–EnabledActionBarActivity

//ActivitySourcecodepublicclassTabNavigationActionBarActivityextendsActivity{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);workwithTabbedActionBar();}publicvoidworkwithTabbedActionBar(){ActionBarbar=this.getActionBar();bar.setTitle(tag);bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);TestTabListenertl=newTestTabListener();Tabtab1=bar.newTab();tab1.setText("Tab1");tab1.setTabListener(tl);bar.addTab(tab1);Tabtab2=bar.newTab();tab2.setText("Tab2");tab2.setTabListener(tl);bar.addTab(tab2);}}//eof-class

Atabbedactionbar,asthenamesuggests,hasmultipletabs.InListing6-17youseethatthereareafewadditionalmethodsandclassesthatareusedtoworkwithtabbedactionbars.Unlikeastandardactionbar,atabbedactionbarrequiresatablistenerforeachtab.ThislistenerneedstoimplementtheTabListenerinterface.InListing6-18theclassTestTabListenerimplementstheTabListenerinterface.IfyouforgettocallthesetTabListener()methodonatabthatisaddedtotheactionbar,yougetaruntimeerrorindicatingthatalistenerisneeded.Listing6-18showsthecodefortheTestTabListenerclass.

Listing6-18.TabListenertoRespondtoTabActions

publicclassTestTabListenerimplementsActionBar.TabListener{//constructorcodepublicTestTabListener(){}//callbackspublicvoidonTabReselected(Tabtab,FragmentTransactionft){//applynecessarylogichere

}publicvoidonTabSelected(Tabtab,FragmentTransactionft){//applynecessarylogichere}publicvoidonTabUnselected(Tabtab,FragmentTransactionft){//applynecessarylogichere}}

Astabsareselectedandunselected,thecallbackmethodsinListing6-18willbecalled.Actionbarisapropertyoftheactivityanddoesnotcrossactivityboundaries.Inotherwords,onecannotuseanactionbartocontrolorinfluencemultipleactivities.Eachactivitymustprovisionitsownactionbars.Anycommonalityofactionsbetweenactionbarsislefttotheprogrammertoorchestrate.

InListing6-17,onceweobtainedtheactionbarforanactivity,wesetitsnavigationmodetoActionBar.NAVIGATION_MODE_TABS.TheothertwopossibleactionbarnavigationmodesareNAVIGATION_MODE_LISTandNAVIGATION_MODE_STANDARD.Let’sseenowhowtoimplementalist-basedactionbar.

ImplementingaList-BasedActionBarTobeabletoinitializeanactionbarwithlistnavigationmode,youneedthefollowingtwothings:

Aspinneradapterthatcanbeusedpopulatethedrop-downlistofnavigationchoices.

Alistnavigationlistenersothatwhenoneofthelistitemsispickedyoucangetacallback.

Listing6-19presentstheSimpleSpinnerArrayAdapterthatimplementstheSpinnerAdapterinterface.Asstatedearlier,thegoalofthisclassistogivealistofitemstoshow.

Listing6-19.CreatingaSpinnerAdapterforListNavigation

publicclassSimpleSpinnerArrayAdapterextendsArrayAdapter<String>implementsSpinnerAdapter{publicSimpleSpinnerArrayAdapter(Contextctx){super(ctx,android.R.layout.simple_spinner_item,newString[]{"one","two"});

this.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);}publicViewgetDropDownView(intposition,ViewconvertView,ViewGroupparent){returnsuper.getDropDownView(position,convertView,parent);}}

ThereisnoSDKclassthatdirectlyimplementstheSpinnerAdapterinterfacerequiredbylistnavigation.So,youderivethisclassfromanArrayAdapterandprovideasimpleimplementationfortheSpinnerAdapter.AttheendofthechapterisareferenceURLonspinneradaptersforfurtherreading.Let’smoveonnowtothelistnavigationlistener.ThisisasimpleclassimplementingtheActionBar.OnNavigationListener.Listing6-20showsthecodeforthisclass.

Listing6-20.CreatingaListListenerforListNavigation

publicclassListListenerimplementsActionBar.OnNavigationListener{//simpleconstructor…publicListListener(){}//neededcallbacktorespondtoactionspublicbooleanonNavigationItemSelected(intitemPosition,longitemId){//respondandreturntruereturntrue;}}

Younowhavewhatyourequiretosetupalistnavigationactionbar.Thesourcecodenecessaryforworkingwithalist-basedactionbarisshowninListing6-21.

Listing6-21.ListNavigationActionBarActivity

//ActivitySourcecodepublicclassTabNavigationActionBarActivityextendsActivity{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);workwithTabbedActionBar();}publicvoidworkwithListActionBar(){ActionBarbar=this.getActionBar();bar.setTitle("title");bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

bar.setListNavigationCallbacks(newSimpleSpinnerArrayAdapter(this),newListListener());}}//eof-class

Figure6-4showshowalistbaractionbarlookswhenexpanded.

Figure6-4.Anactivitywithanopenednavigationlist

Thatconcludeshowwecanuseanactionbarforregularmenus,tabbednavigation,andlist-basednavigation.Let’sseenowhowwecanembedasearchviewliketheoneshowninFigure6-2.

ExploringActionBarandSearchViewThissectionshowshowtouseasearchwidgetintheactionbar.Youneedthefollowingtousesearchinyouractionbar:

1. DefineamenuiteminamenuXMLfilepointingtoasearchviewclassprovidedbytheSDK.Youalsoneedanactivityintowhichyoucanloadthismenu.Thisisoftencalledthesearchinvokeractivity.

2. Createanotheractivitythatcantakethequeryfromthesearchviewinstep1andprovideresults.Thisisoftencalledthesearchresultsactivity.

3. CreateanXMLfilethatallowsyoutocustomizethesearchviewintheactionbar.Thisfileisoftencalledsearchable.xmlandresidesintheres/xmlsubdirectory.

4. Declarethesearchresultsactivityinthemanifestfile.ThisdefinitionneedstopointtotheXMLfiledefinedinstep3.

5. Inyourmenusetupforthesearchinvokeractivity,indicatethatthesearchviewneedstotargetthesearchresultsactivityfromstep2.

Let’sstartwiththeSearchviewwidget.

DefiningaSearchViewWidgetasaMenuItemTodefineasearchviewtoappearintheactionbarofyouractivity,youneedtodefinea

menuiteminoneofyourmenuXMLfiles,asshowninListing6-22.

Listing6-22.SearchViewMenuItemDefinition

<itemandroid:id="@+id/menu_search"android:title="Search"android:showAsAction="ifRoom"

android:actionViewClass="android.widget.SearchView"

/>

ThekeyelementinListing6-22istheactionViewClassattributepointingtoandroid.widget.SearchView.Yousawtheotherattributesearlierinthechapterwhenyoudeclaredyournormalmenuitemstoappearasactioniconsintheactionbar.

CreatingaSearchResultsActivityToenablesearchinyourapplication,youneedanactivitythatcanrespondtoasearchquery.Thiscanbelikeanyotheractivity.AnexampleisshowninListing6-23.

Listing6-23.SearchResultsActivity

publicclassSearchResultsActivityextendsActivity{publicstaticStringtag="SearchResultsActivity";@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);finalIntentqueryIntent=getIntent();doSearchQuery(queryIntent);}@OverridepublicvoidonNewIntent(finalIntentnewIntent){super.onNewIntent(newIntent);finalIntentqueryIntent=getIntent();doSearchQuery(queryIntent);}privatevoiddoSearchQuery(finalIntentqueryIntent){finalStringqueryAction=queryIntent.getAction();if(!(Intent.ACTION_SEARCH.equals(queryAction))){Log.d(tag,"intentNOTforsearch");return;}finalStringqueryString=queryIntent.getStringExtra(SearchManager.QUERY);Log.d(tag,queryString);}}//eof-class

InListing6-23,theactivitycheckstoseewhethertheactionthatinvokeditisinitiatedby

search.Or,thisactivitycouldhavebeennewlycreatedorjustbroughttothetop,inwhichcaseitneedstodosomethingidenticaltotheonCreate()methodinitsonNewIntent()methodaswell.Ontheotherhand,ifthisactivityisinvokedbysearch,itretrievesthequerystringusinganextraparametercalledSearchManager.QUERY.Thentheactivitylogswhatthatstringis.Inarealscenario,youwouldusethatstringtopaintmatchingresults.

SpecifyingaSearchableXMLFileAsindicatedintheearliersteps,let’slookattheXMLfilethatisrequiredandcustomizesthesearchwidget;seeListing6-24.

Listing6-24.SearchableXMLFile

<!--/res/xml/searchable.xml--><searchablexmlns:android="http://schemas.android.com/apk/res/android"android:label="@string/search_label"android:hint="@string/search_hint"/>

Thehintattributewillappearonthesearchviewwidgetasahintthatdisappearswhenyoustarttyping.Thelabeldoesn’tplayasignificantroleintheactionbar.However,whenyouusethesamesearchresultsactivityinasearchdialog,thedialoghasthelabeldefinedhere.YoucanlearnmoreaboutsearchableXMLattributesatthefollowingURL:

http://developer.android.com/guide/topics/search/searchable-config.html

DefiningtheSearchResultsActivityintheManifestFileNowlet’sseehowtotiethisXMLfiletothesearchresultsactivity.Thisisdoneinthemanifestfileaspartofdefiningthesearchresultsactivity:seeListing6-25.NoticethemetadatadefinitionpointingtothesearchableXMLfileresource.

Listing6-25.TyinganActivitytoItsSearchable.xml

<activityandroid:name=".SearchResultsActivity"android:label="SearchResults"><intent-filter><actionandroid:name="android.intent.action.SEARCH"/></intent-filter><meta-dataandroid:name="android.app.searchable"android:resource="@xml/searchable"/></activity>

IdentifyingtheSearchTargetfortheSearchViewWidgetSofar,youhavethesearchviewinyouractionbar,andyouhavetheactivitythatcanrespondtosearch.YouneedtotietogetherthesetwopiecesusingJavacode.YoudothisintheonCreateOptions()callbackofthesearch-invokingactivityaspartofsettingupyourmenu.ThefunctioninListing6-26canbecalledfromonCreateOptions()tolinkthesearchviewwidgetandthesearchresultsactivity.

Listing6-26.TyingtheSearchViewWidgettotheSearchResultsActivity

privatevoidsetupSearchView(Menumenu){//Step1:LocatethesearchviewwidgetSearchViewsearchView=(SearchView)menu.findItem(R.id.menu_search).getActionView();//reporterrorandreturnifsearchViewisnull

//Step2:getSearchManagerandsearchableInfoSearchManagersearchManager=(SearchManager)getSystemService(Context.SEARCH_SERVICE);ComponentNamecn=newComponentName(this,SearchResultsActivity.class);SearchableInfoinfo=searchManager.getSearchableInfo(cn);//reporterrorandreturnifsearchableinfoisnull

//Step3:setsearchableInfoonthesearchviewwidgetsearchView.setSearchableInfo(info);//Donoticonifythewidget;expanditbydefaultsearchView.setIconifiedByDefault(false);}

Let’swalkthroughwhatishappeninginListing6-26.Thegoalofthiscodeistotellthesearchviewwhereitcanfindthesearchable.xmlthatdefinesthesearchbehavior.Todothis,thefirststepistogetareferencetotheSearchView.ThisisdonethroughtheMenuobject.Thesecondstepistoaskthesystem-widesearchmanagerwhatsearchableXMLfileistiedtotheactivitySearchResultsActivity.ThisisdonebycallingthemethodgetSearchableInfoontheSearchManagersystemservice.OncewehavetheSearchableInfoobjectrepresentingtheXMLfile,wepassthatinformationtotheSearchViewobject.Withallthisinplace,nowifyoutypesomethinginthesearchbox,thatinformationwillbepassedtothesearchresultsactivity,whichwillshowtheresults.

AndroidSearchAPIisalargeAPIwithalotofnuancesthat,duetospace,wehavenotincludedinthisbook.Therearethreesuggestions.WehaveprovidedaURLinthe“Resources”sectionthatpointstoaseriesofarticlesandnotesontheGooglesearchAPI.

WealsohavealargechapteronSearchfromthepreviouseditionmadeavailableonline.Thelinktothisisalsointhe“Resources”section.WehavealsoupdatedthatSearchmaterialfromthepreviouseditionandaddedthatcontenttotheExpertAndroideditionfromApress.

ResourcesAsyoulearnaboutandworkwithAndroidmenusandactionbars,youmaywanttokeepthefollowingURLshandy:

http://developer.android.com/guide/topics/ui/menus.htmlPrimarydocumentfromGoogledescribinghowtoworkwithmenus.

http://developer.android.com/guide/topics/resources/menu-resource.html:InformationaboutvariousXMLtagsyoucanuseinamenuresource.

http://developer.android.com/reference/android/app/ActionBar.htmlAPIURLfortheActionBarclass.

http://www.androidbook.com/item/3624:Ourresearchonactionbar,includingalistoffurtherreferences,samplecode,linkstoexamples,andUIfiguresrepresentingvariousactionbarmodes.

http://www.androidbook.com/item/3627:Tosetuplistnavigationmode,youneedtounderstandhowdrop-downlistsandspinnerswork.ThisbriefarticleshowsafewsamplesandreferencelinksonhowtousespinnersinAndroid.

http://www.androidbook.com/item/3885:Explainshowsearchworks,tohelpyouutilizetheactionbartoitsfullextent.

http://www.androidicons.com:Websitefromwhichacoupleoftheiconsusedinthischapterareborrowed.TheseiconsareunderCreativeCommonsLicense3.0.

http://www.androidbook.com/item/3302:“PleasingAndroidLayouts.”Afewnotesandsamplecodeforsimplelayouts.

http://www.androidbook.com/item/4060:YouwillfindhereafreecopyoftheSearchchapterfromthepreviousedition.ThisprovidesextensivecoverageonAndroidsearch.

http://androidbook.com/proandroid5/projects:ProjectdownloadURLforthisbook.ThedownloadableprojectZIPfilesforthischapterareProAndroid5_ch06_TestMenus.zipandProAndroid5_ch06_TestActionBar.zip.

SummaryMenusandactionbarsareanintegralpartofwritingmobileapps.Thischaptercoversregularmenus,contextmenus,pop-upmenus,standardactionbars,tabbedactionbars,andlist-basedactionbars.Thischapteralsocoversthebasicsofhowtoembedasearchviewwidgetinanactionbar.

Chapter7

StylesandThemesThusfar,wehavecoveredsomefundamentalsoftheAndroiduserinterface(UI).Inthischapter,wearegoingtodiscussstylesandthemes,whichhelptoencapsulatecontrol-appearanceattributesforeasiersetupandmaintenance.Androidprovidesseveralwaystoalterthestyleofviewsinyourapplication,inXMLandincode.We’llfirstcoverusingmarkuptagsinstringsandthenhowtousespannablestochangespecificvisualattributesoftext.Butwhatifyouwanttocontrolhowthingslookusingacommonspecificationforseveralviewsoracrossanentireactivityorapplication?We’lldiscussAndroidstylesandthemestoshowyouhow.

UsingStylesSometimes,youwanttohighlightorstyleaportionoftheView’scontent.Youcandothisstaticallyordynamically.Statically,youcanapplymarkupdirectlytothestringsinyourstringresources,asshownhere:

<stringname="styledText"><i>Static</i>styleina<b>TextView</b>.</string>

YoucanthenreferenceitinyourXMLorfromcode.NotethatyoucanusethefollowingHTMLtagswithstringresources:<i>,<b>,and<u>,foritalics,bold,andunderlined,respectively,aswellas<sup>(superscript),<sub>(subscript),<strike>(strikethrough),<big>,<small>,and<monospace>.Youcanevennestthesetoget,forexample,smallsuperscripts.ThisworksnotjustinTextViewsbutalsoinotherviews,likebuttons.Figure7-1showswhatstyledandthemedtextlookslike,usingmanyoftheexamplesinthissection.

Figure7-1.Examplesofstylesandthemes

StylingaTextViewcontrol’scontentprogrammaticallyrequiresalittleadditionalworkbutallowsformuchmoreflexibility(seeListing7-1),becauseyoucanstyleitatruntime.Thisflexibilitycanonlybeappliedtoaspannable,though,whichishowEditTextnormallymanagestheinternaltext,whereasTextViewdoesnotnormallyuseSpannable.SpannableisbasicallyaStringthatyoucanapplystylesto.TogetaTextViewtostoretextasaspannable,youcancallsetTextthisway:

tv.setText("ThistextisstoredinaSpannable",TextView.BufferType.SPANNABLE);

Then,whenyoucalltv.getText,you’llgetaspannable.

AsshowninListing7-1,youcangetthecontentoftheEditText(asaSpannableobject)andthensetstylesforportionsofthetext.Thecodeinthelistingsetsthetextstylingtoboldanditalicsandsetsthebackgroundtored.YoucanuseallthestylingoptionsaswehavewiththeHTMLtagsasdescribedpreviously,andthensome.

Listing7-1.ApplyingStylesDynamicallytotheContentofanEditText

EditTextet=(EditText)this.findViewById(R.id.et);

et.setText("StylingthecontentofanEditTextdynamically");Spannablespn=(Spannable)et.getText();spn.setSpan(newBackgroundColorSpan(Color.RED),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);spn.setSpan(newStyleSpan(android.graphics.Typeface.BOLD_ITALIC),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Thesetwotechniquesforstylingonlyworkontheoneviewthey’reappliedto.Androidprovidesastylemechanismtodefineacommonstyletobereusedacrossviews,aswellasathememechanism,whichbasicallyappliesastyletoanentireactivityortheentireapplication.Tobeginwith,weneedtotalkaboutstyles.

AstyleisacollectionofViewattributesthatisgivenanamesoyoucanrefertothatcollectionbyitsnameandassignthatstylebynametoviews.Forexample,Listing7-2showsaresourceXMLfile,savedin/res/values,thatwecoulduseforallerrormessages.

Listing7-2.DefiningaStyletoBeUsedAcrossManyViews

<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText"><itemname="android:layout_width">fill_parent</item><itemname="android:layout_height">wrap_content</item><itemname="android:textColor">#FF0000</item><itemname="android:typeface">monospace</item></style></resources>

Thesizeoftheviewisdefinedaswellasthefontcolor(red)andtypeface.Noticehowthenameattributeoftheitemtag(e.g.,android:layout_width)istheXMLattributenameweusedinourlayoutXMLfilesinearlierchapters,andthevalueoftheitemtagnolongerrequiresdoublequotes.WecannowusethisstyleforanerrorTextView,asshowninListing7-3.

Listing7-3.UsingaStyleinaView

<TextViewandroid:id="@+id/errorText"style="@style/ErrorText"android:text="Noerrorsatthistime"/>

ItisimportanttonotethattheattributenameforastyleinthisViewdefinitiondoesnotstartwithandroid:.Watchoutforthis,becauseeverythingseemstouseandroid:exceptthestyle.Whenyou’vegotmanyviewsinyourapplicationthatshareastyle,changingthatstyleinoneplaceismuchsimpler;youonlyneedtomodifythestyle’sattributesintheoneresourcefile.

Youcan,ofcourse,createmanydifferentstylesforvariouscontrols.Forexample,buttonscouldshareacommonstylethatisdifferentfromthecommonstylefortextinmenus.Itiscommontoseetextattributesmanagedwithstyles,includingandroid:textColor,android:textStyle,andandroid:textSize.Othercommonattributesusedwithstylesincludethepaddingvalues,android:background,andcolors.

Onereallyniceaspectofstylesisthatyoucansetupahierarchyofthem.WecoulddefineanewstyleforreallybaderrormessagesandbaseitonthestyleofErrorText.Listing7-4showshowthismightlook.

Listing7-4.DefiningaStylefromaParentStyle

<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText.Danger"><itemname="android:textStyle">bold</item></style></resources>

Thisexampleshowsthatwecansimplynameourchildstyleusingtheparentstyleasaprefixtothenewstylename.Therefore,ErrorText.DangerisachildofErrorTextandinheritsthestyleattributesoftheparent.ItthenaddsanewattributefortextStyle.Thiscanberepeatedagainandagaintocreateawholetreeofstyles.

Aswasthecaseforadapterlayouts,Androidprovidesalargesetofstylesthatwecanuse.TospecifyanAndroid-providedstyle,usesyntaxlikethis:

style="@android:style/TextAppearance"

ThisstylesetsthedefaultstylefortextinAndroid.TolocatethemasterAndroidstyles.xmlfile,visittheAndroidSDK/platforms/<android-version>/data/res/values/folder,whereyouinstalledtheAndroidSDK;<android-version>istheparticularversionofAndroidyouwanttoseestylesfor.Insidethisfile,youwillfindquiteafewstylesthatareready-madeforyoutouseorextend.Aquicknoteabout@android:style/TextAppearance:thisstyledoesnotsetandroid:layout_heightorandroid:layout_width,soaViewspecificationwouldneedmorethanthisstyletocompileproperly.

Here’sawordofcautionaboutextendingtheAndroid-providedstyles:thepreviousmethodofusingaprefixwon’tworkwithAndroid-providedstyles.Instead,youmustusetheparentattributeofthestyletag,likethis:

<stylename="CustomTextAppearance"parent="@android:style/TextAppearance"><item...yourextensionsgohere…/></style>

Youdon’talwayshavetopullinanentirestyleonyourview.Youcouldchoosetoborrowjustapartofthestyleinstead.Forexample,ifyouwanttosetthecolorofthetextinyour

TextViewtoasystemstylecolor,youcoulddothefollowing:

<TextViewandroid:id="@+id/tv2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textColor="?android:textColorSecondary"android:text="@string/hello_world"/>

Noticethatinthisexample,thenameofthetextColorattributevaluestartswiththe?characterinsteadofthe@character.The?characterisusedsoAndroidknowstolookforastylevalueinthecurrenttheme.Becausewesee?android,welookintheAndroidsystemthemeforthisstylevalue.

UsingThemesOneproblemwithstylesisthatyouneedtoaddanattributespecificationofstyle=”@style/…”toeveryviewdefinitionthatyouwantittoapplyto.Ifyouhavesomestyleelementsyouwantappliedacrossanentireactivity,oracrossthewholeapplication,youshoulduseathemeinstead.Athemeisreallyjustastyleappliedbroadly;butintermsofdefiningatheme,it’sexactlylikeastyle.Infact,themesandstylesarefairlyinterchangeable:youcanextendathemeintoastyleorrefertoastyleasatheme.Typically,onlythenamesgiveahintastowhetherastyleisintendedtobeusedasastyleoratheme.

Tospecifyathemeforanactivityoranapplication,youwouldaddanattributetothe<activity>or<application>tagintheAndroidManifest.xmlfileforyourproject.Thecodemightlooklikeoneofthese:

<activityandroid:theme="@style/MyActivityTheme"><applicationandroid:theme="@style/MyApplicationTheme"><applicationandroid:theme="@android:style/Theme.NoTitleBar">

YoucanfindtheAndroid-providedthemesinthesamefolderastheAndroid-providedstyles,withthethemesinafilecalledthemes.xml.Whenyoulookinsidethethemesfile,youwillseealargesetofstylesdefined,withnamesthatstartwithTheme.Itmightbegoodtoreadthatlastlineafewtimes.Putanotherway,allstylesandthemesareoftypestyle,evenifthestylenamehas“Theme”init.YouwillalsonoticethatwithintheAndroid-providedthemesandstyles,thereisalotofextendinggoingon,whichiswhyyouendupwithstylescalledTheme.Dialog.AppError,forexample.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistof

downloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch07_Styles.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.

http://developer.android.com/guide/topics/ui/themes.htmlTheAndroidguidetostylesandthemes.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutstylesandthemes:

Stylesarejustcollectionsofviewattributesforeasyreuseacrossviews,activities,andapplications.

Youcanmakeyourownstyles,useapredefinedstyle,orextendanexistingstyle.

Themesarewhatyoucallastylewhenitisappliedtoanactivityorapplication.

Chapter8

FragmentsSofar,we’veexploredseveralbitsandpiecesofanAndroidapplication,andyou’verunsomesimpleapplicationstailoredtoasmartphone-sizedscreen.AllyouhadtothinkaboutwashowtolayouttheUIcontrolsonthescreenforanactivity,andhowoneactivityflowedtothenext,andsoon.ForthefirsttwomajorreleasesofAndroid,smallscreenswereit.ThencametheAndroidtablets:deviceswithscreensizesof10”.Andthatcomplicatedthings.Why?Becausenowtherewassomuchscreenrealestatethatasimpleactivityhadahardtimefillingascreenwhileatthesametimekeepingtoasinglefunction.Itnolongermadesensetohaveane-mailapplicationthatshowedonlyheadersinoneactivity(fillingalargescreen),andaseparateactivitytoshowanindividuale-mail(alsofillingalargescreen).Withthatmuchroomtoworkwith,anapplicationcouldshowalistofe-mailheadersdowntheleftsideofthescreenandtheselectede-mailcontentsontherightsideofthescreen.Coulditbedoneinasingleactivitywithasinglelayout?Well,yes,butyoucouldn’treusethatactivityorlayoutforanyofthesmaller-screendevices.

OneofthecoreclassesintroducedinAndroid3.0wastheFragmentclass,especiallydesignedtohelpdevelopersmanageapplicationfunctionalitysoitwouldprovidegreatusabilityaswellaslotsofreuse.Thischapterwillintroduceyoutothefragment,whatitis,howitfitsintoanapplication’sarchitecture,andhowtouseit.Fragmentsmakealotofinterestingthingspossiblethatweredifficultbefore.Ataboutthesametime,GooglereleasedafragmentSDKthatworksonoldAndroids.Soevenifyouweren’tinterestedinwritingapplicationsfortablets,youmayhavefoundthatfragmentsmadeyourlifeeasieronnon-tabletdevices.Nowit’seasierthanevertowritegreatapplicationsforsmartphonesandtabletsandevenTVsandotherdevices.

Let’sgetstartedwithAndroidfragments.

WhatIsaFragment?Thisfirstsectionwillexplainwhatafragmentisandwhatitdoes.Butfirst,let’ssetthestagetoseewhyweneedfragments.Asyoulearnedearlier,anAndroidapplicationonsmall-screendevicesusesactivitiestoshowdataandfunctionalitytoauser,andeachactivityhasafairlysimple,well-definedpurpose.Forexample,anactivitymightshowtheuseralistofcontactsfromtheiraddressbook.Anotheractivitymightallowtheusertotypeane-mail.TheAndroidapplicationistheseriesoftheseactivitiesgroupedtogethertoachievealargerpurpose,suchasmanagingane-mailaccountviathereadingandsendingofmessages.Thisisfineforasmall-screendevice,butwhentheuser’sscreenisverylarge(10”orlarger),there’sroomonthescreentodomorethanjustonesimplething.Anapplicationmightwanttolettheuserviewthelistofe-mailsintheirinboxandatthesametimeshowthecurrentlyselectede-mailtextnexttothelist.Oranapplicationmightwanttoshowalistofcontactsandatthesametimeshowthecurrentlyselectedcontactin

adetailview.

AsanAndroiddeveloper,youknowthatthisfunctionalitycouldbeaccomplishedbydefiningyetanotherlayoutforthexlargescreenwithListViewsandlayoutsandallsortsofotherviews.Andby“yetanotherlayout”wemeanlayoutsinadditiontothoseyou’veprobablyalreadydefinedforthesmallerscreens.Ofcourse,you’llwanttohaveseparatelayoutsfortheportraitcaseaswellasthelandscapecase.Andwiththesizeofanxlargescreen,thiscouldmeanquiteafewviewsforallthelabelsandfieldsandimagesandsoonthatyou’llneedtolayoutandthenprovidecodefor.Ifonlytherewereawaytogrouptheseviewobjectstogetherandconsolidatethelogicforthem,sothatchunksofanapplicationcouldbereusedacrossscreensizesanddevices,minimizinghowmuchworkadeveloperhastodotomaintaintheirapplication.Andthatiswhywehavefragments.

Onewaytothinkofafragmentisasasub-activity.Andinfact,thesemanticsofafragmentarealotlikeanactivity.Afragmentcanhaveaviewhierarchyassociatedwithit,andithasalifecyclemuchlikeanactivity’slifecycle.FragmentscanevenrespondtotheBackbuttonlikeactivitiesdo.Ifyouwerethinking,“IfonlyIcouldputmultipleactivitiestogetheronatablet’sscreenatthesametime,”thenyou’reontherighttrack.Butbecauseitwouldbetoomessytohavemorethanoneactivityofanapplicationactiveatthesametimeonatabletscreen,fragmentswerecreatedtoimplementbasicallythatthought.Thismeansfragmentsarecontainedwithinanactivity.Fragmentscanonlyexistwithinthecontextofanactivity;youcan’tuseafragmentwithoutanactivity.Fragmentscancoexistwithotherelementsofanactivity,whichmeansyoudonotneedtoconverttheentireuserinterfaceofyouractivitytousefragments.Youcancreateanactivity’slayoutasbeforeandonlyuseafragmentforonepieceoftheuserinterface.

Fragmentsarenotlikeactivities,however,whenitcomestosavingstateandrestoringitlater.Thefragmentsframeworkprovidesseveralfeaturestomakesavingandrestoringfragmentsmuchsimplerthantheworkyouneedtodoonactivities.

Howyoudecidewhentouseafragmentdependsonafewconsiderations,whicharediscussednext.

WhentoUseFragmentsOneoftheprimaryreasonstouseafragmentissoyoucanreuseachunkofuserinterfaceandfunctionalityacrossdevicesandscreensizes.Thisisespeciallytruewithtablets.Thinkofhowmuchcanhappenwhenthescreenisaslargeasatablet’s.It’smorelikeadesktopthanaphone,andmanyofyourdesktopapplicationshaveamultipaneuserinterface.Asdescribedearlier,youcanhavealistandadetailviewoftheselecteditemonscreenatthesametime.Thisiseasytopictureinalandscapeorientationwiththelistontheleftandthedetailsontheright.Butwhatiftheuserrotatesthedevicetoportraitmodesothatnowthescreenistallerthanitiswide?Perhapsyounowwantthelisttobeinthetopportionofthescreenandthedetailsinthebottomportion.Butwhatifthisapplicationisrunningonasmallscreenandthere’sjustnoroomforthetwoportionstobeonthescreenatthesametime?Wouldn’tyouwanttheseparateactivitiesforthelistandforthedetailstobeabletosharethelogicyou’vebuiltintotheseportionsforalargescreen?Wehopeyouansweredyes.Fragmentscanhelpwiththat.Figure8-1makesthisalittle

clearer.

Figure8-1.FragmentsusedforatabletUIandforasmartphoneUI

Inlandscapemode,twofragmentsmaysitnicelysidebyside.Inportraitmode,wemightbeabletoputonefragmentabovetheother.Butifwe’retryingtorunthesameapplicationonadevicewithasmallerscreen,wemightneedtoshoweitherfragment1orfragment2butnotbothatthesametime.Ifwetriedtomanageallthesescenarioswithlayouts,we’dbecreatingquiteafew,whichmeansdifficultytryingtokeepeverythingcorrectacrossmanyseparatelayouts.Whenusingfragments,ourlayoutsstaysimple;eachactivitylayoutdealswiththefragmentsascontainers,andtheactivitylayoutsdon’tneedtospecifytheinternalstructureofeachfragment.Eachfragmentwillhaveitsownlayoutforitsinternalstructureandcanbereusedacrossmanyconfigurations.

Let’sgobacktotherotatingorientationexample.Ifyou’vehadtocodefororientationchangesofanactivity,youknowthatitcanbearealpaintosavethecurrentstateoftheactivityandtorestorethestateoncetheactivityhasbeenre-created.Wouldn’titbeniceifyouractivityhadchunksthatcouldbeeasilyretainedacrossorientationchanges,soyoucouldavoidallthetearingdownandre-creatingeverytimetheorientationchanged?Ofcourseitwould.Fragmentscanhelpwiththat.

Nowimaginethatauserisinyouractivity,andthey’vebeendoingsomework.Andimaginethattheuserinterfacehaschangedwithinthesameactivity,andtheuserwantstogobackastep,ortwo,orthree.Inanold-styleactivity,pressingtheBackbuttonwilltaketheuseroutoftheactivityentirely.Withfragments,theBackbuttoncanstepbackwardthroughastackoffragmentswhilestayinginsidethecurrentactivity.

Next,thinkaboutanactivity’suserinterfacewhenabigchunkofcontentchanges;you’dliketomakethetransitionlooksmooth,likeapolishedapplication.Fragmentscandothat,too.

Nowthatyouhavesomeideaofwhatafragmentisandwhyyou’dwanttouseone,let’sdigalittledeeperintothestructureofafragment.

TheStructureofaFragmentAsmentioned,afragmentislikeasub-activity:ithasafairlyspecificpurposeandalmost

alwaysdisplaysauserinterface.ButwhereanactivityissubclassedfromContext,afragmentisextendedfromObjectinpackageandroid.app.AfragmentisnotanextensionofActivity.Likeactivities,however,youwillalwaysextendFragment(oroneofitssubclasses)soyoucanoverrideitsbehavior.

Afragmentcanhaveaviewhierarchytoengagewithauser.Thisviewhierarchyislikeanyotherviewhierarchyinthatitcanbecreated(inflated)fromanXMLlayoutspecificationorcreatedincode.Theviewhierarchyneedstobeattachedtotheviewhierarchyofthesurroundingactivityifitistobeseenbytheuser,whichyou’llgettoshortly.Theviewobjectsthatmakeupafragment’sviewhierarchyarethesamesortsofviewsthatareusedelsewhereinAndroid.Soeverythingyouknowaboutviewsappliestofragmentsaswell.

Besidestheviewhierarchy,afragmenthasabundlethatservesasitsinitializationarguments.Similartoanactivity,afragmentcanbesavedandlaterrestoredautomaticallybythesystem.Whenthesystemrestoresafragment,itcallsthedefaultconstructor(withnoarguments)andthenrestoresthisbundleofargumentstothenewlycreatedfragment.Subsequentcallbacksonthefragmenthaveaccesstotheseargumentsandcanusethemtogetthefragmentbacktoitspreviousstate.Forthisreason,itisimperativethatyou

Ensurethatthere’sadefaultconstructorforyourfragmentclass.

Addabundleofargumentsassoonasyoucreateanewfragmentsothesesubsequentmethodscanproperlysetupyourfragment,andsothesystemcanrestoreyourfragmentproperlywhennecessary.

Anactivitycanhavemultiplefragmentsinplayatonetime;andifafragmenthasbeenswitchedoutwithanotherfragment,thefragment-switchingtransactioncanbesavedonabackstack.Thebackstackismanagedbythefragmentmanagertiedtotheactivity.ThebackstackishowtheBackbuttonbehaviorismanaged.Thefragmentmanagerisdiscussedlaterinthischapter.Whatyouneedtoknowhereisthatafragmentknowswhichactivityitistiedto,andfromthereitcangettoitsfragmentmanager.Afragmentcanalsogettotheactivity’sresourcesthroughitsactivity.

Alsosimilartoanactivity,afragmentcansavestateintoabundleobjectwhenthefragmentisbeingre-created,andthisbundleobjectgetsgivenbacktothefragment’sonCreate()callback.ThissavedbundleisalsopassedtoonInflate(),onCreateView(),andonActivityCreated().Notethatthisisnotthesamebundleastheoneattachedasinitializationarguments.Thisbundleisoneinwhichyouarelikelytostorethecurrentstateofthefragment,notthevaluesthatshouldbeusedtoinitializeit.

AFragment’sLifeCycleBeforeyoustartusingfragmentsinsampleapplications,youneedunderstandthelifecycleofafragment.Why?Afragment’slifecycleismorecomplicatedthananactivity’slifecycle,andit’sveryimportanttounderstandwhenyoucandothingswithfragments.Figure8-2showsthelifecycleofafragment.

Figure8-2.Lifecycleofafragment

IfyoucomparethistoFigure2-3(thelifecycleforanactivity),you’llnoticeseveraldifferences,duemostlytotheinteractionrequiredbetweenanactivityandafragment.Afragmentisverydependentontheactivityinwhichitlivesandcangothroughmultiplestepswhileitsactivitygoesthroughone.

Attheverybeginning,afragmentisinstantiated.Itnowexistsasanobjectinmemory.Thefirstthingthatislikelytohappenisthatinitializationargumentswillbeaddedtoyourfragmentobject.Thisisdefinitelytrueinthesituationwherethesystemisre-creatingyourfragmentfromasavedstate.Whenthesystemisrestoringafragmentfromasavedstate,thedefaultconstructorisinvoked,followedbytheattachmentoftheinitializationargumentsbundle.Ifyouaredoingthecreationofthefragmentincode,anicepatterntouseisthatinListing8-1,whichshowsafactorytypeofinstantiatorwithintheMyFragmentclassdefinition.

Listing8-1.InstantiatingaFragmentUsingaStaticFactoryMethod

publicstaticMyFragmentnewInstance(intindex){MyFragmentf=newMyFragment();Bundleargs=newBundle();args.putInt("index",index);f.setArguments(args);

returnf;}

Fromtheclient’spointofview,theygetanewinstancebycallingthestaticnewInstance()methodwithasingleargument.Theygettheinstantiatedobjectback,andtheinitializationargumenthasbeensetonthisfragmentintheargumentsbundle.Ifthisfragmentissavedandreconstructedlater,thesystemwillgothroughaverysimilarprocessofcallingthedefaultconstructorandthenreattachingtheinitializationarguments.Foryourparticularcase,youwoulddefinethesignatureofyournewInstance()method(ormethods)totaketheappropriatenumberandtypeofarguments,andthenbuildtheargumentsbundleappropriately.ThisisallyouwantyournewInstance()methodtodo.Thecallbacksthatfollowwilltakecareoftherestofthesetupofyourfragment.

TheonInflate()CallbackThenextthingthathappensislayoutviewinflation.Ifyourfragmentisdefinedbya<fragment>taginalayout,yourfragment’sonInflate()callbackwillbecalled.Thispassesinareferencetothesurroundingactivity,anAttributeSetwiththeattributesfromthe<fragment>tag,andasavedbundle.Thesavedbundleistheonewiththesavedstatevaluesinit,puttherebyonSaveInstanceState()ifthisfragmentexistedbeforeandisbeingre-created.TheexpectationofonInflate()isthatyou’llreadattributevaluesandsavethemforlateruse.Atthisstageinthefragment’slife,it’stooearlytoactuallydoanythingwiththeuserinterface.Thefragmentisnotevenassociatedtoitsactivityyet.Butthat’sthenexteventtooccurtoyourfragment.

TheonAttach()CallbackTheonAttach()callbackisinvokedafteryourfragmentisassociatedwithitsactivity.Theactivityreferenceispassedtoyouifyouwanttouseit.Youcanatleastusetheactivitytodetermineinformationaboutyourenclosingactivity.Youcanalsousetheactivityasacontexttodootheroperations.OnethingtonoteisthattheFragmentclasshasagetActivity()methodthatwillalwaysreturntheattachedactivityforyourfragmentshouldyouneedit.Keepinmindthatallduringthislifecycle,theinitializationargumentsbundleisavailabletoyoufromthefragment’sgetArguments()method.However,oncethefragmentisattachedtoitsactivity,youcan’tcallsetArguments()again.Therefore,youcan’taddtotheinitializationargumentsexceptintheverybeginning.

TheonCreate()CallbackNextupistheonCreate()callback.Althoughthisissimilartotheactivity’sonCreate(),thedifferenceisthatyoushouldnotputcodeinherethatreliesontheexistenceoftheactivity’sviewhierarchy.Yourfragmentmaybeassociatedtoitsactivitybynow,butyouhaven’tyetbeennotifiedthattheactivity’sonCreate()hasfinished.That’scomingup.Thiscallbackgetsthesavedstatebundlepassedin,ifthereisone.Thiscallbackisaboutasearlyaspossibletocreateabackgroundthreadtogetdatathatthis

fragmentwillneed.YourfragmentcodeisrunningontheUIthread,andyoudon’twanttododiskinput/output(I/O)ornetworkaccessesontheUIthread.Infact,itmakesalotofsensetofireoffabackgroundthreadtogetthingsready.Yourbackgroundthreadiswhereblockingcallsshouldbe.You’llneedtohookupwiththedatalater,perhapsusingahandlerorsomeothertechnique.

NoteOneofthewaystoloaddatainabackgroundthreadistousetheLoaderclass.ThiswillbecoveredinChapter28.

TheonCreateView()CallbackThenextcallbackisonCreateView().Theexpectationhereisthatyouwillreturnaviewhierarchyforthisfragment.TheargumentspassedintothiscallbackincludeaLayoutInflater(whichyoucanusetoinflatealayoutforthisfragment),aViewGroupparent(calledcontainerinListing8-2),andthesavedbundleifoneexists.ItisveryimportanttonotethatyoushouldnotattachtheviewhierarchytotheViewGroupparentpassedin.Thatassociationwillhappenautomaticallylater.Youwillverylikelygetexceptionsifyouattachthefragment’sviewhierarchytotheparentinthiscallback—oratleastoddandunexpectedapplicationbehavior.

Listing8-2.CreatingaFragmentViewHierarchyinonCreateView()

@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){if(container==null)returnnull;

Viewv=inflater.inflate(R.layout.details,container,false);TextViewtext1=(TextView)v.findViewById(R.id.text1);text1.setText(myDataSet[getPosition()]);returnv;}

Theparentisprovidedsoyoucanuseitwiththeinflate()methodoftheLayoutInflater.Iftheparentcontainervalueisnull,thatmeansthisparticularfragmentwon’tbeviewedbecausethere’snoviewhierarchyforittoattachto.Inthiscase,youcansimplyreturnnullfromhere.Rememberthattheremaybefragmentsfloatingaroundinyourapplicationthataren’tbeingdisplayed.Listing8-2showsasampleofwhatyoumightwanttodointhismethod.

HereyouseehowyoucanaccessalayoutXMLfilethatisjustforthisfragmentandinflateittoaviewthatyoureturntothecaller.Thereareseveraladvantagestothisapproach.Youcouldalwaysconstructtheviewhierarchyincode,butbyinflatingalayout

XMLfile,you’retakingadvantageofthesystem’sresource-findinglogic.Dependingonwhichconfigurationthedeviceisin,orforthatmatterwhichdeviceyou’reon,theappropriatelayoutXMLfilewillbechosen.Youcanthenaccessaparticularviewwithinthelayout—inthiscase,thetext1TextViewfield—todowhatyouwantwith.Torepeataveryimportantpoint:donotattachthefragment’sviewtothecontainerparentinthiscallback.YoucanseeinListing8-2thatyouuseacontainerinthecalltoinflate(),butyoualsopassfalsefortheattachToRootparameter.

TheonViewCreated()CallbackThisoneiscalledrightafteronCreateView()butbeforeanysavedstatehasbeenputintotheUI.TheviewobjectpassedinisthesameviewobjectthatgotreturnedfromonCreateView().

TheonActivityCreated()CallbackYou’renowgettingclosetothepointwheretheusercaninteractwithyourfragment.ThenextcallbackisonActivityCreated().ThisiscalledaftertheactivityhascompleteditsonCreate()callback.Youcannowtrustthattheactivity’sviewhierarchy,includingyourownviewhierarchyifyoureturnedoneearlier,isreadyandavailable.Thisiswhereyoucandofinaltweakstotheuserinterfacebeforetheuserseesit.It’salsowhereyoucanbesurethatanyotherfragmentforthisactivityhasbeenattachedtoyouractivity.

TheonViewStateRestored()CallbackThisoneisrelativelynew,introducedwithJellyBean4.2.Yourfragmentwillhavethiscallbackcalledwhentheviewhierarchyofthisfragmenthasallstaterestored(ifapplicable).PreviouslyyouhadtomakedecisionsinonActivityCreated()abouttweakingtheUIforarestoredfragment.Nowyoucanputthatlogicinthiscallbackknowingdefinitelythatthisfragmentisbeingrestoredfromasavedstate.

TheonStart()CallbackThenextcallbackinyourfragmentlifecycleisonStart().Nowyourfragmentisvisibletotheuser.Butyouhaven’tstartedinteractingwiththeuserjustyet.Thiscallbackistiedtotheactivity’sonStart().Assuch,whereaspreviouslyyoumayhaveputyourlogicintotheactivity’sonStart(),nowyou’remorelikelytoputyourlogicintothefragment’sonStart(),becausethatisalsowheretheuserinterfacecomponentsare.

TheonResume()CallbackThelastcallbackbeforetheusercaninteractwithyourfragmentisonResume().Thiscallbackistiedtotheactivity’sonResume().Whenthiscallbackreturns,theuserisfreetointeractwiththisfragment.Forexample,ifyouhaveacamerapreviewinyourfragment,youwouldprobablyenableitinthefragment’sonResume().

Sonowyou’vereachedthepointwheretheappisbusilymakingtheuserhappy.Andthentheuserdecidestogetoutofyourapp,eitherbyBack’ingout,orbypressingtheHomebutton,orbylaunchingsomeotherapplication.Thenextsequence,similartowhathappenswithanactivity,goesintheoppositedirectionofsettingupthefragmentforinteraction.

TheonPause()CallbackThefirstundocallbackonafragmentisonPause().Thiscallbackistiedtotheactivity’sonPause();justaswithanactivity,ifyouhaveamediaplayerinyourfragmentorsomeothersharedobject,youcouldpauseit,stopit,orgiveitbackviayouronPause()method.Thesamegood-citizenrulesapplyhere:youdon’twanttobeplayingaudioiftheuseristakingaphonecall.

TheonSaveInstanceState()CallbackSimilartoactivities,fragmentshaveanopportunitytosavestateforlaterreconstruction.ThiscallbackpassesinaBundleobjectforthisfragmenttobeusedasthecontainerforwhateverstateinformationyouwanttohangonto.Thisisthesaved-statebundlepassedtothecallbackscoveredearlier.Topreventmemoryproblems,becarefulaboutwhatyousaveintothisbundle.Onlysavewhatyouneed.Ifyouneedtokeepareferencetoanotherfragment,don’ttrytosaveorputtheotherfragment,ratherjustsavetheidentifierfortheotherfragmentsuchasitstagorID.WhenthisfragmentrunsonViewStateRestored(),thenyoucouldre-establishconnectionstotheotherfragmentsthatthisfragmentdependson.

AlthoughyoumayseethismethodusuallycalledrightafteronPause(),theactivitytowhichthisfragmentbelongscallsitwhenitfeelsthatthefragment’sstateshouldbesaved.ThiscanoccuranytimebeforeonDestroy().

TheonStop()CallbackThenextundocallbackisonStop().Thisoneistiedtotheactivity’sonStop()andservesapurposesimilartoanactivity’sonStop().AfragmentthathasbeenstoppedcouldgostraightbacktotheonStart()callback,whichthenleadstoonResume().

TheonDestroyView()CallbackIfyourfragmentisonitswaytobeingkilledofforsaved,thenextcallbackintheundodirectionisonDestroyView().ThiswillbecalledaftertheviewhierarchyyoucreatedonyouronCreateView()callbackearlierhasbeendetachedfromyourfragment.

TheonDestroy()CallbackNextupisonDestroy().Thisiscalledwhenthefragmentisnolongerinuse.Notethatitisstillattachedtotheactivityandisstillfindable,butitcan’tdomuch.

TheonDetach()CallbackThefinalcallbackinafragment’slifecycleisonDetach().Oncethisisinvoked,thefragmentisnottiedtoitsactivity,itdoesnothaveaviewhierarchyanymore,andallitsresourcesshouldhavebeenreleased.

UsingsetRetainInstance()YoumayhavenoticedthedottedlinesinthediagraminFigure8-2.Oneofthecoolfeaturesofafragmentisthatyoucanspecifythatyoudon’twantthefragmentcompletelydestroyediftheactivityisbeingre-createdandthereforeyourfragmentswillbecomingbackalso.Therefore,FragmentcomeswithamethodcalledsetRetainInstance(),whichtakesabooleanparametertotellit“Yes;Iwantyoutohangaroundwhenmyactivityrestarts”or“No;goaway,andI’llcreateanewfragmentfromscratch.”AgoodplacetocallsetRetainInstance()isintheonCreate()callbackofafragment,butinonCreateView()works,asdoesonActivityCreated().

Iftheparameteristrue,thatmeansyouwanttokeepyourfragmentobjectinmemoryandnotstartoverfromscratch.However,ifyouractivityisgoingawayandbeingre-created,you’llhavetodetachyourfragmentfromthisactivityandattachittothenewone.Thebottomlineisthatiftheretaininstancevalueistrue,youwon’tactuallydestroyyourfragmentinstance,andthereforeyouwon’tneedtocreateanewoneontheotherside.ThedottedlinesonthediagrammeanyouwouldskiptheonDestroy()callbackonthewayout,you’dskiptheonCreate()callbackwhenyourfragmentisbeingre-attachedtoyournewactivity,andallothercallbackswouldfire.Becauseanactivityisre-createdmostlikelyforconfigurationchanges,yourfragmentcallbacksshouldprobablyassumethattheconfigurationhaschanged,andthereforeshouldtakeappropriateaction.ThiswouldincludeinflatingthelayouttocreateanewviewhierarchyinonCreateView(),forexample.ThecodeprovidedinListing8-2wouldtakecareofthatasitiswritten.Ifyouchoosetousetheretain-instancefeature,youmaydecidenottoputsomeofyourinitializationlogicinonCreate()becauseitwon’talwaysgetcalledthewaytheothercallbackswill.

SampleFragmentAppShowingtheLifeCycleThere’snothinglikeseeingarealexampletogetanappreciationforaconcept.You’lluseasampleapplicationthathasbeeninstrumentedsoyoucanseeallthesecallbacksinaction.You’regoingtoworkwithasampleapplicationthatusesalistofShakespeareantitlesinonefragment;whentheuserclicksoneofthetitles,sometextfromthatplaywillappearinaseparatefragment.Thissampleapplicationwillworkinbothlandscapeandportraitmodesonatablet.Thenyou’llconfigureittorunasifonasmallerscreensoyoucanseehowtoseparatethetextfragmentintoanactivity.You’llstartwiththeXMLlayoutofyouractivityinlandscapemodeinListing8-3,whichwilllooklikeFigure8-3whenitruns.

Listing8-3.YourActivity’sLayoutXMLforLandscapeMode

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout-land/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="match_parent">

<fragmentclass="com.androidbook.fragments.bard.TitlesFragment"android:id="@+id/titles"android:layout_weight="1"android:layout_width="0px"android:layout_height="match_parent"/><FrameLayoutandroid:id="@+id/details"android:layout_weight="2"android:layout_width="0px"android:layout_height="match_parent"/>

</LinearLayout>

Figure8-3.Theuserinterfaceofyoursamplefragmentapplication

NoteAttheendofthechapteristheURLyoucanusetodownloadtheprojects

inthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDE(suchasEclipseorAndroidStudio)directly.

Thislayoutlookslikealotofotherlayoutsyou’veseenthroughoutthebook,horizontallylefttorightwithtwomainobjects.There’saspecialnewtag,though,called<fragment>,andthistaghasanewattributecalledclass.Keepinmindthatafragmentisnotaview,sothelayoutXMLisalittledifferentforafragmentthanitisforeverythingelse.Theotherthingtokeepinmindisthatthe<fragment>tagisjustaplaceholderinthislayout.Youshouldnotputchildtagsunder<fragment>inalayoutXMLfile.

Theotherattributesforafragmentlookfamiliarandserveapurposesimilartothatforaview.Thefragmenttag’sclassattributespecifiesyourextendedclassforthetitlesofyourapplication.Thatis,youmustextendoneoftheAndroidFragmentclassestoimplementyourlogic,andthe<fragment>tagmustknowthenameofyourextendedclass.Afragmenthasitsownviewhierarchythatwillbecreatedlaterbythefragmentitself.ThenexttagisaFrameLayout—notanother<fragment>tag.Whyisthat?We’llexplaininmoredetaillater,butfornow,youshouldbeawarethatyou’regoingtobedoingsometransitionsonthetext,swappingoutonefragmentwithanother.YouusetheFrameLayoutastheviewcontainertoholdthecurrenttextfragment.Withyourtitlesfragment,youhaveone—andonlyone—fragmenttoworryabout:noswappingandnotransitions.FortheareathatdisplaystheShakespeareantext,you’llhaveseveralfragments.

TheMainActivityJavacodeisinListing8-4.Actually,thelistingonlyshowstheinterestingcode.Thecodeisinstrumentedwithloggingmessagessoyoucanseewhat’sgoingonthroughLogCat.PleasereviewthesourcecodefilesforShakespeareInstrumentedfromthewebsitetoseeallofit.

Listing8-4.InterestingSourceCodefromMainActivity

publicbooleanisMultiPane(){returngetResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE;}

/***Helperfunctiontoshowthedetailsofaselecteditem,eitherby*displayingafragmentin-placeinthecurrentUI,orstartinga*wholenewactivityinwhichitisdisplayed.*/publicvoidshowDetails(intindex){Log.v(TAG,"inMainActivityshowDetails("+index+")");

if(isMultiPane()){//Checkwhatfragmentisshown,replaceifneeded.

DetailsFragmentdetails=(DetailsFragment)getFragmentManager().findFragmentById(R.id.details);if((details==null)||(details.getShownIndex()!=index)){//Makenewfragmenttoshowthisselection.details=DetailsFragment.newInstance(index);

//Executeatransaction,replacinganyexisting//fragmentwiththisoneinsidetheframe.Log.v(TAG,"abouttorunFragmentTransaction…");FragmentTransactionft=getFragmentManager().beginTransaction();ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//ft.addToBackStack("details");ft.replace(R.id.details,details);ft.commit();}

}else{//Otherwiseyouneedtolaunchanewactivitytodisplay//thedialogfragmentwithselectedtext.Intentintent=newIntent();intent.setClass(this,DetailsActivity.class);intent.putExtra("index",index);startActivity(intent);}}

Thisisaverysimpleactivitytowrite.Todeterminemultipanemode(thatis,whetheryouneedtousefragmentssidebyside),youjustusetheorientationofthedevice.Ifyou’reinlandscapemode,you’remultipane;ifyou’reinportraitmode,you’renot.ThehelpermethodshowDetails()istheretofigureouthowtoshowthetextwhenatitleisselected.Theindexisthepositionofthetitleinthetitlelist.Ifyou’reinmultipanemode,you’regoingtouseafragmenttoshowthetext.You’recallingthisfragmentaDetailsFragment,andyouuseafactory-typemethodtocreateonewiththeindex.TheinterestingcodefortheDetailsFragmentclassisshowninListing8-5(minusalloftheloggingcode).AswedidbeforeinTitlesFragment,thevariouscallbacksofDetailsFragmenthaveloggingaddedsowecanwatchwhathappensviaLogCat.You’llcomebacktoyourshowDetails()methodlater.

Listing8-5.SourceCodeforDetailsFragment

publicclassDetailsFragmentextendsFragment{

privateintmIndex=0;

publicstaticDetailsFragmentnewInstance(intindex){Log.v(MainActivity.TAG,"inDetailsFragmentnewInstance("+index+")");

DetailsFragmentdf=newDetailsFragment();

//Supplyindexinputasanargument.Bundleargs=newBundle();args.putInt("index",index);df.setArguments(args);returndf;}

publicstaticDetailsFragmentnewInstance(Bundlebundle){intindex=bundle.getInt("index",0);returnnewInstance(index);}

@OverridepublicvoidonCreate(BundlemyBundle){Log.v(MainActivity.TAG,"inDetailsFragmentonCreate.Bundlecontains:");if(myBundle!=null){for(Stringkey:myBundle.keySet()){Log.v(MainActivity.TAG,""+key);}}else{Log.v(MainActivity.TAG,"myBundleisnull");}super.onCreate(myBundle);

mIndex=getArguments().getInt("index",0);}

publicintgetShownIndex(){returnmIndex;}

@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){Log.v(MainActivity.TAG,

"inDetailsFragmentonCreateView.container="+container);

//Don'ttiethisfragmenttoanythingthroughtheinflater.//Androidtakescareofattachingfragmentsforus.The//containerisonlypassedinsoyoucanknowaboutthe//containerwherethisViewhierarchyisgoingtogo.Viewv=inflater.inflate(R.layout.details,container,false);TextViewtext1=(TextView)v.findViewById(R.id.text1);text1.setText(Shakespeare.DIALOGUE[mIndex]);returnv;}}

TheDetailsFragmentclassisactuallyfairlysimpleaswell.Nowyoucanseehowtoinstantiatethisfragment.It’simportanttopointoutthatyou’reinstantiatingthisfragmentincodebecauseyourlayoutdefinestheViewGroupcontainer(aFrameLayout)thatyourdetailsfragmentisgoingtogointo.BecausethefragmentisnotitselfdefinedinthelayoutXMLfortheactivity,asyourtitlesfragmentwas,youneedtoinstantiateyourdetailsfragmentsincode.

Tocreateanewdetailsfragment,youuseyournewInstance()method.Asdiscussedearlier,thisfactorymethodinvokesthedefaultconstructorandthensetstheargumentsbundlewiththevalueofindex.OncenewInstance()hasrun,yourdetailsfragmentcanretrievethevalueofindexinanyofitscallbacksbyreferringtotheargumentsbundleviagetArguments().Foryourconvenience,inonCreate()youcansavetheindexvaluefromtheargumentsbundletoamemberfieldinyourDetailsFragmentclass.

Youmightwonderwhyyoudidn’tsimplysetthemIndexvalueinnewInstance().ThereasonisthatAndroidwill,behindthescenes,re-createyourfragmentusingthedefaultconstructor.Thenitsetstheargumentsbundletowhatitwasbefore.Androidwon’tuseyournewInstance()method,sotheonlyreliablewaytoensurethatmIndexissetistoreadthevaluefromtheargumentsbundleandsetitinonCreate().TheconveniencemethodgetShownIndex()retrievesthevalueofthatindex.NowtheonlymethodlefttodescribeinthedetailsfragmentisonCreateView().Andthisisverysimple,too.

ThepurposeofonCreateView()istoreturntheviewhierarchyforyourfragment.Rememberthatbasedonyourconfiguration,youcouldwantallkindsofdifferentlayoutsforthisfragment.Therefore,themostcommonthingtodoisutilizealayoutXMLfilefor

yourfragment.Inyoursampleapplication,youspecifythelayoutforthefragmenttobedetails.xmlusingtheresourceR.layout.details.TheXMLfordetails.xmlisinListing8-6.

Listing8-6.Thedetails.xmlLayoutFilefortheDetailsFragment

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/details.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ScrollViewandroid:id="@+id/scroller"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/text1"android:layout_width="match_parent"android:layout_height="match_parent"/></ScrollView></LinearLayout>

Foryoursampleapplication,youcanusetheexactsamelayoutfilefordetailswhetheryou’reinlandscapemodeorinportraitmode.Thislayoutisnotfortheactivity,it’sjustforyourfragmenttodisplaythetext.Becauseitcouldbeconsideredthedefaultlayout,youcanstoreitinthe/res/layoutdirectoryanditwillbefoundandusedevenifyou’reinlandscapemode.WhenAndroidgoeslookingforthedetailsXMLfile,ittriesthespecificdirectoriesthatcloselymatchthedevice’sconfiguration,butitwillendupinthe/res/layoutdirectoryifitcan’tfindthedetails.xmlfileinanyoftheotherplaces.Ofcourse,ifyouwanttohaveadifferentlayoutforyourfragmentinlandscapemode,youcoulddefineaseparatedetails.xmllayoutfileandstoreitunder/res/layout-land.Feelfreetoexperimentwithdifferentdetails.xmlfiles.

Whenyourdetailsfragment’sonCreateView()iscalled,youwillsimplygrabtheappropriatedetails.xmllayoutfile,inflateit,andsetthetexttothetextfromtheShakespeareclass.TheentireJavacodeforShakespeareisnotshownhere,butaportionisinListing8-7soyouunderstandhowitwasdone.Forthecompletesource,accesstheprojectdownloadfiles,asdescribedinthe“References”sectionattheendofthischapter.

Listing8-7.SourceCodeforShakespeare.java

publicclassShakespeare{publicstaticStringTITLES[]={"HenryIV(1)","HenryV","HenryVIII","RomeoandJuliet","Hamlet",

"TheMerchantofVenice","Othello"};publicstaticStringDIALOGUE[]={"Soshakenasweare,sowanwithcare,\n…...andsoon…

Nowyourdetailsfragmentviewhierarchycontainsthetextfromtheselectedtitle.Yourdetailsfragmentisreadytogo.AndyoucanreturntoMainActivity’sshowDetails()methodtotalkaboutFragmentTransactions.

FragmentTransactionsandtheFragmentBackStackThecodeinshowDetails()thatpullsinyournewdetailsfragment(partiallyshownagaininListing8-8)looksrathersimple,butthere’salotgoingonhere.It’sworthspendingsometimetoexplainwhatishappeningandwhy.Ifyouractivityisinmultipanemode,youwanttoshowthedetailsinafragmentnexttothetitlelist.Youmayalreadybeshowingdetails,whichmeansyoumayhaveadetailsfragmentvisibletotheuser.Eitherway,theresourceIDR.id.detailsisfortheFrameLayoutforyouractivity,asshowninListing8-3.Ifyouhaveadetailsfragmentsittinginthelayoutbecauseyoudidn’tassignanyotherIDtoit,itwillhavethisID.Therefore,tofindoutifthere’sadetailsfragmentinthelayout,youcanaskthefragmentmanagerusingfindFragmentById().Thiswillreturnnulliftheframelayoutisemptyorwillgiveyouthecurrentdetailsfragment.Youcanthendecideifyouneedtoplaceanewdetailsfragmentinthelayout,eitherbecausethelayoutisemptyorbecausethere’sadetailsfragmentforsomeothertitle.Onceyoumakethedeterminationtocreateanduseanewdetailsfragment,youinvokethefactorymethodtocreateanewinstanceofadetailsfragment.Nowyoucanputthisnewfragmentintoplacefortheusertosee.

Listing8-8.FragmentTransactionExample

publicvoidshowDetails(intindex){Log.v(TAG,"inMainActivityshowDetails("+index+")");

if(isMultiPane()){//Checkwhatfragmentisshown,replaceifneeded.DetailsFragmentdetails=(DetailsFragment)getFragmentManager().findFragmentById(R.id.details);if(details==null||details.getShownIndex()!=index){//Makenewfragmenttoshowthisselection.details=DetailsFragment.newInstance(index);

//Executeatransaction,replacinganyexisting

//fragmentwiththisoneinsidetheframe.Log.v(TAG,"abouttorunFragmentTransaction…");FragmentTransactionft=getFragmentManager().beginTransaction();ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//ft.addToBackStack("details");ft.replace(R.id.details,details);ft.commit();}//Therestwasleftouttosavespace.}

Akeyconcepttounderstandisthatafragmentmustliveinsideaviewcontainer,alsoknownasaviewgroup.TheViewGroupclassincludessuchthingsaslayoutsandtheirderivedclasses.FrameLayoutisagoodchoiceasthecontainerforthedetailsfragmentinthemain.xmllayoutfileofyouractivity.AFrameLayoutissimple,andallyouneedisasimplecontainerforyourfragment,withouttheextrabaggagethatcomeswithothertypesoflayouts.TheFrameLayoutiswhereyourdetailsfragmentisgoingtogo.Ifyouhadinsteadspecifiedanother<fragment>tagintheactivity’slayoutfileinsteadofaFrameLayout,youwouldnotbeabletoreplacethecurrentfragmentwithanewfragment(i.e.,swapfragments).

TheFragmentTransactioniswhatyouusetodoyourswapping.Youtellthefragmenttransactionthatyouwanttoreplacewhateverisinyourframelayoutwithyournewdetailsfragment.YoucouldhaveavoidedallthisbylocatingtheresourceIDofthedetailsTextViewandjustsettingthetextofittothenewtextforthenewShakespearetitle.Butthere’sanothersidetofragmentsthatexplainswhyyouuseFragmentTransactions.

Asyouknow,activitiesarearrangedinastack,andasyougetdeeperanddeeperintoanapplication,it’snotuncommontohaveastackofseveralactivitiesgoingatonce.WhenyoupresstheBackbutton,thetopmostactivitygoesaway,andyouarereturnedtotheactivitybelow,whichresumesforyou.Thiscancontinueuntilyou’reatthehomescreenagain.

Thiswasfinewhenanactivitywasjustsingle-purpose,butnowthatanactivitycanhaveseveralfragmentsgoingatonce,andbecauseyoucangodeeperintoyourapplicationwithoutleavingthetopmostactivity,AndroidreallyneededtoextendtheBackbuttonstackconcepttoincludefragmentsaswell.Infact,fragmentsdemandthisevenmore.Whenthereareseveralfragmentsinteractingwitheachotheratthesametimeinanactivity,andthere’satransitiontonewcontentacrossseveralfragmentsatonce,pressingtheBackbuttonshouldcauseeachofthefragmentstorollbackonesteptogether.Toensurethateachfragmentproperlyparticipatesintherollback,aFragmentTransactioniscreatedandmanagedtoperformthatcoordination.

Beawarethatabackstackforfragmentsisnotrequiredwithinanactivity.YoucancodeyourapplicationtolettheBackbuttonworkattheactivitylevelandnotatthefragment

levelatall.Ifthere’snobackstackforyourfragments,pressingtheBackbuttonwillpopthecurrentactivityoffthestackandreturntheusertowhateverwasunderneath.Ifyouchoosetotakeadvantageofthebackstackforfragments,youwillwanttouncommentinListing8-8thelinethatsaysft.addToBackStack(“details”).Forthisparticularcase,you’vehardcodedthetagparametertobethestring“details”.Thistagshouldbeanappropriatestringnamethatrepresentsthestateofthefragmentsatthetimeofthetransaction.Thetagisnotnecessarilyanameforaspecificfragmentbutratherforthefragmenttransactionandallthefragmentsinthetransaction.Youwillbeabletointerrogatethebackstackincodeusingthetagvaluetodeleteentries,aswellaspopentriesoff.Youwillwantmeaningfultagsonthesetransactionstobeabletofindtheappropriateoneslater.

FragmentTransactionTransitionsandAnimationsOneoftheverynicethingsaboutfragmenttransactionsisthatyoucanperformtransitionsfromanoldfragmenttoanewfragmentusingtransitionsandanimations.Thesearenotliketheanimationscominglater,inChapter18.Thesearemuchsimpleranddonotrequirein-depthgraphicsknowledge.Let’suseafragmenttransactiontransitiontoaddspecialeffectswhenyouswapouttheolddetailsfragmentwithanewdetailsfragment.Thiscanaddpolishtoyourapplication,makingtheswitchfromtheoldtothenewfragmentlooksmooth.

OnemethodtoaccomplishthisissetTransition(),asshowninListing8-8.However,thereareafewdifferenttransitionsavailable.Youusedafadeinyourexample,butyoucanalsousethesetCustomAnimations()methodtodescribeotherspecialeffects,suchasslidingonefragmentouttotherightasanotherslidesinfromtheleft.Thecustomanimationsusethenewobjectanimationdefinitions,nottheoldones.TheoldanimXMLfilesusetagssuchas<translate>,whereasthenewXMLfilesuse<objectAnimator>.TheoldstandardXMLfilesarelocatedinthe/data/res/animdirectoryundertheappropriateAndroidSDKplatformsdirectory(suchasplatforms/android-11forHoneycomb).TherearesomenewXMLfileslocatedinthe/data/res/animatordirectoryhere,too.Yourcodecouldbesomethinglike

ft.setCustomAnimations(android.R.animator.fade_in,android.R.animator.fade_out);

whichwillcausethenewfragmenttofadeinastheoldfragmentfadesout.Thefirstparameterappliestothefragmententering,andthesecondparameterappliestothefragmentexiting.FeelfreetoexploretheAndroidanimatordirectoryformorestockanimations.Ifyou’dliketocreateyourown,there’ssectionontheobjectanimatorinChapter18tohelpyou.Theotherveryimportantbitofknowledgeyouneedisthatthetransitioncallsneedtocomebeforethereplace()call;otherwise,theywillhavenoeffect.

Usingtheobjectanimatorforspecialeffectsonfragmentscanbeafunwaytodotransitions.TherearetwoothermethodsonFragmentTransactionyoushouldknow

about:hide()andshow().Bothofthesemethodstakeafragmentasaparameter,andtheydoexactlywhatyou’dexpect.Forafragmentinthefragmentmanagerassociatedtoaviewcontainer,themethodssimplyhideorshowthefragmentintheuserinterface.Thefragmentdoesnotgetremovedfromthefragmentmanagerintheprocess,butitcertainlymustbetiedintoaviewcontainerinordertoaffectitsvisibility.Ifafragmentdoesnothaveaviewhierarchy,orifitsviewhierarchyisnottiedintothedisplayedviewhierarchy,thenthesemethodswon’tdoanything.

Onceyou’vespecifiedthespecialeffectsforyourfragmenttransaction,youhavetotellitthemainworkthatyouwantdone.Inyourcase,you’rereplacingwhateverisintheframelayoutwithyournewdetailsfragment.That’swherethereplace()methodcomesin.Thisisequivalenttocallingremove()foranyfragmentsthatarealreadyintheframelayoutandthenadd()foryournewdetailsfragment,whichmeansyoucouldjustcallremove()oradd()asneededinstead.

Thefinalactionyoumusttakewhenworkingwithafragmenttransactionistocommitit.Thecommit()methoddoesnotcausethingstohappenimmediatelybutratherschedulestheworkforwhentheUIthreadisreadytodoit.

Nowyoushouldunderstandwhyyouneedtogotosomuchtroubletochangethecontentinasimplefragment.It’snotjustthatyouwanttochangethetext;youmightwantaspecialgraphicseffectduringthetransition.Youmayalsowanttosavethetransitiondetailsinafragmenttransactionthatyoucanreverselater.Thatlastpointmaybeconfusing,sowe’llclarify.

Thisisnotatransactioninthetruestsenseoftheword.Whenyoupopfragmenttransactionsoffthebackstack,youarenotundoingallthedatachangesthatmayhavetakenplace.Ifdatachangedwithinyouractivity,forexample,asyoucreatedfragmenttransactionsonthebackstack,pressingtheBackbuttondoesnotcausetheactivitydatachangestorevertbacktotheirpreviousvalues.Youaremerelysteppingbackthroughtheuserinterfaceviewsthewayyoucamein,justasyoudowithactivities,butinthiscaseit’sforfragments.Becauseofthewayfragmentsaresavedandrestored,theinnerstateofafragmentthathasbeenrestoredfromasavedstatewilldependonwhatvaluesyousavedwiththefragmentandhowyoumanagetorestorethem.Soyourfragmentsmaylookthesameastheydidpreviouslybutyouractivitywillnot,unlessyoutakestepstorestoreactivitystatewhenyourestorefragments.

Inyourexample,you’reonlyworkingwithoneviewcontainerandbringinginonedetailsfragment.Ifyouruserinterfaceweremorecomplicated,youcouldmanipulateotherfragmentswithinthefragmenttransaction.Whatyouareactuallydoingisbeginningthetransaction,replacinganyexistingfragmentinyourdetailsframelayoutwithyournewdetailsfragment,specifyingafade-inanimation,andcommittingthetransaction.Youcommentedoutthepartwherethistransactionisaddedtothebackstack,butyoucouldcertainlyuncommentittotakepartinthebackstack.

TheFragmentManager

TheFragmentManagerisacomponentthattakescareofthefragmentsbelongingtoanactivity.Thisincludesfragmentsonthebackstackandfragmentsthatmayjustbehangingaround.We’llexplain.

Fragmentsshouldonlybecreatedwithinthecontextofanactivity.Thisoccurseitherthroughtheinflationofanactivity’slayoutXMLorthroughdirectinstantiationusingcodelikethatinListing8-1.Wheninstantiatedthroughcode,afragmentusuallygetsattachedtotheactivityusingafragmenttransaction.Ineithercase,theFragmentManagerclassisusedtoaccessandmanagethesefragmentsforanactivity.

YouusethegetFragmentManager()methodoneitheranactivityoranattachedfragmenttoretrieveafragmentmanager.YousawinListing8-8thatafragmentmanageriswhereyougetafragmenttransaction.Besidesgettingafragmenttransaction,youcanalsogetafragmentusingthefragment’sID,itstag,oracombinationofbundleandkey.Thefragment’sIDwilleitherbethefragment’sresourceIDifthefragmentwasinflatedfromXML,oritwillbethecontainer’sresourceIDifthefragmentwasplacedintoaviewusingafragmenttransaction.Afragment’stagisaStringthatyoucanassigninthefragment’sXMLdefinition,orwhenthefragmentisplacedinaviewviaafragmenttransaction.ThebundleandkeymethodofretrievingafragmentonlyworksforfragmentsthatwerepersistedusingtheputFragment()method.

Forgettingafragment,thegettermethodsincludefindFragmentById(),findFragmentByTag(),andgetFragment().ThegetFragment()methodwouldbeusedinconjunctionwithputFragment(),whichalsotakesabundle,akey,andthefragmenttobeput.ThebundleismostlikelygoingtobethesavedStatebundle,andputFragment()willbeusedintheonSaveInstanceState()callbacktosavethestateofthecurrentactivity(oranotherfragment).ThegetFragment()methodwouldprobablybecalledinonCreate()tocorrespondtoputFragment(),althoughforafragment,thebundleisavailabletotheothercallbackmethods,asdescribedearlier.

Obviously,youcan’tusethegetFragmentManager()methodonafragmentthathasnotbeenattachedtoanactivityyet.Butit’salsotruethatyoucanattachafragmenttoanactivitywithoutmakingitvisibletotheuseryet.Ifyoudothis,youshouldassociateaStringtagtothefragmentsoyoucangettoitinthefuture.You’dmostlikelyusethismethodofFragmentTransactiontodothis:

publicFragmentTransactionadd(Fragmentfragment,Stringtag)

Infact,youcanhaveafragmentthatdoesnotexhibitaviewhierarchy.Thismightbedonetoencapsulatecertainlogictogethersuchthatitcouldbeattachedtoanactivity,yetstillretainsomeautonomyfromtheactivity’slifecycleandfromotherfragments.Whenanactivitygoesthroughare-createcycleduetoadevice-configurationchange,thisnon-UIfragmentcouldremainlargelyintactwhiletheactivitygoesawayandcomesbackagain.ThiswouldbeagoodcandidateforthesetRetainInstance()option.

Thefragmentbackstackisalsothedomainofthefragmentmanager.Whereasafragment

transactionisusedtoputfragmentsontothebackstack,thefragmentmanagercantakefragmentsoffthebackstack.Thisisusuallydoneusingthefragment’sIDortag,butitcanbedonebasedonpositioninthebackstackorjusttopopthetopmostfragment.

Finally,thefragmentmanagerhasmethodsforsomedebuggingfeatures,suchasturningondebuggingmessagestoLogCatusingenableDebugLogging()ordumpingthecurrentstateofthefragmentmanagertoastreamusingdump().NotethatyouturnedonfragmentmanagerdebuggingintheonCreate()methodofyouractivityinListing8-4.

CautionWhenReferencingFragmentsIt’stimetorevisittheearlierdiscussionofthefragment’slifecycleandtheargumentsandsaved-statebundles.Androidcouldsaveoneofyourfragmentsatmanydifferenttimes.Thismeansthatatthemomentyourapplicationwantstoretrievethatfragment,it’spossiblethatitisnotinmemory.Forthisreason,wecautionyounottothinkthatavariablereferencetoafragmentisgoingtoremainvalidforalongtime.Iffragmentsarebeingreplacedinacontainerviewusingfragmenttransactions,anyreferencetotheoldfragmentisnowpointingtoafragmentthatispossiblyonthebackstack.Orafragmentmaygetdetachedfromtheactivity’sviewhierarchyduringanapplicationconfigurationchangesuchasascreenrotation.Becareful.

Ifyou’regoingtoholdontoareferencetoafragment,beawareofwhenitcouldgetsavedaway;whenyouneedtofinditagain,useoneofthegettermethodsofthefragmentmanager.Ifyouwanttohangontoafragmentreference,suchaswhenanactivityisgoingthroughaconfigurationchange,youcanusetheputFragment()methodwiththeappropriatebundle.Inthecaseofbothactivitiesandfragments,theappropriatebundleisthesavedStatebundlethatisusedinonSaveInstanceState()andthatreappearsinonCreate()(or,inthecaseoffragments,theotherearlycallbacksofthefragment’slifecycle).Youwillprobablyneverstoreadirectfragmentreferenceintotheargumentsbundleofafragment;ifyou’retemptedtodoso,pleasethinkverycarefullyaboutitfirst.

TheotherwayyoucangettoaspecificfragmentisbyqueryingforitusingaknowntagorknownID.Thegettermethodsdescribedpreviouslywillallowretrievaloffragmentsfromthefragmentmanagerthisway,whichmeansyouhavetheoptionofjustrememberingthetagorIDofafragmentsothatyoucanretrieveitfromthefragmentmanagerusingoneofthosevalues,asopposedtousingputFragment()andgetFragment().

SavingFragmentStateAnotherinterestingclasswasintroducedinAndroid3.2:Fragment.SavedState.UsingthesaveFragmentInstanceState()methodofFragmentManager,youcanpassthismethodafragment,anditreturnsanobjectrepresentingthestateofthatfragment.Youcanthenusethatobjectwheninitializingafragment,usingFragment’ssetInitialSavedState()method.Chapter9discussesthisinmoredetail.

ListFragmentsand<fragment>Therearestillafewmorethingstocovertomakeyoursampleapplicationcomplete.ThefirstistheTitlesFragmentclass.Thisistheonethatiscreatedviathemain.xmlfileofyourmainactivity.The<fragment>tagservesasyourplaceholderforwherethisfragmentwillgoanddoesnotdefinewhattheviewhierarchywilllooklikeforthisfragment.TheinterestingcodeforyourTitlesFragmentisinListing8-9.Forallofthecodepleaserefertothesourcecodefiles.TitlesFragmentdisplaysthelistoftitlesforyourapplication.

Listing8-9.TitlesFragmentJavaCode

publicclassTitlesFragmentextendsListFragment{privateMainActivitymyActivity=null;intmCurCheckPosition=0;

@OverridepublicvoidonAttach(ActivitymyActivity){Log.v(MainActivity.TAG,"inTitlesFragmentonAttach;activityis:"+myActivity);super.onAttach(myActivity);this.myActivity=(MainActivity)myActivity;}

@OverridepublicvoidonActivityCreated(BundlesavedState){Log.v(MainActivity.TAG,"inTitlesFragmentonActivityCreated.savedStatecontains:");if(savedState!=null){for(Stringkey:savedState.keySet()){Log.v(MainActivity.TAG,""+key);}}else{Log.v(MainActivity.TAG,"savedStateisnull");}super.onActivityCreated(savedState);

//Populatelistwithyourstaticarrayoftitles.setListAdapter(newArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1,Shakespeare.TITLES));

if(savedState!=null){//Restorelaststateforcheckedposition.

mCurCheckPosition=savedState.getInt("curChoice",0);}

//GetyourListFragment'sListViewandupdateitListViewlv=getListView();lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);lv.setSelection(mCurCheckPosition);

//Activityiscreated,fragmentsareavailable//GoaheadandpopulatethedetailsfragmentmyActivity.showDetails(mCurCheckPosition);}@OverridepublicvoidonSaveInstanceState(BundleoutState){Log.v(MainActivity.TAG,"inTitlesFragmentonSaveInstanceState");super.onSaveInstanceState(outState);outState.putInt("curChoice",mCurCheckPosition);}

@OverridepublicvoidonListItemClick(ListViewl,Viewv,intpos,longid){Log.v(MainActivity.TAG,"inTitlesFragmentonListItemClick.pos="+pos);myActivity.showDetails(pos);mCurCheckPosition=pos;}

@OverridepublicvoidonDetach(){Log.v(MainActivity.TAG,"inTitlesFragmentonDetach");super.onDetach();myActivity=null;}}

UnlikeDetailsFragment,forthisfragmentyoudon’tdoanythingintheonCreateView()callback.Thisisbecauseyou’reextendingtheListFragmentclass,whichcontainsaListViewalready.ThedefaultonCreateView()foraListFragmentcreatesthisListViewforyouandreturnsit.It’snotuntilonActivityCreated()thatyoudoanyrealapplicationlogic.Bythistimeinyourapplication,youcanbesurethattheactivity’sviewhierarchy,plusthisfragment’s,hasbeencreated.TheresourceIDforthatListViewisandroid.R.id.list1,butyou

canalwayscallgetListView()ifyouneedtogetareferencetoit,whichyoudoinonActivityCreated().BecauseListFragmentmanagestheListView,donotattachtheadaptertotheListViewdirectly.YoumustusetheListFragment’ssetListAdapter()methodinstead.Theactivity’sviewhierarchyisnowsetup,soyou’resafegoingbackintotheactivitytodotheshowDetails()call.

Atthispointinyoursampleactivity’slife,you’veaddedalistadaptertoyourlistview,you’verestoredthecurrentposition(ifyoucamebackfromarestore,dueperhapstoaconfigurationchange),andyou’veaskedtheactivity(inshowDetails())tosetthetexttocorrespondtotheselectedShakespeareantitle.

YourTitlesFragmentclassalsohasalisteneronthelistsowhentheuserclicksanothertitle,theonListItemClick()callbackiscalled,andyouswitchthetexttocorrespondtothattitle,againusingtheshowDetails()method.

Anotherdifferencebetweenthisfragmentandtheearlierdetailsfragmentisthatwhenthisfragmentisbeingdestroyedandre-created,yousavestateinabundle(thevalueofthecurrentpositioninthelist),andyoureaditbackinonCreate().UnlikethedetailsfragmentsthatgetswappedinandoutoftheFrameLayoutonyouractivity’slayout,thereisjustonetitlesfragmenttothinkabout.Sowhenthereisaconfigurationchangeandyourtitlesfragmentisgoingthroughasave-and-restoreoperation,youwanttorememberwhereyouwere.Withthedetailsfragments,youcanre-createthemwithouthavingtorememberthepreviousstate.

InvokingaSeparateActivityWhenNeededThere’sapieceofcodewehaven’ttalkedaboutyet,andthatisinshowDetails()whenyou’reinportraitmodeandthedetailsfragmentwon’tfitproperlyonthesamepageasthetitlesfragment.Ifthescreenrealestatewon’tpermitfeasibleviewingofafragmentthatwouldotherwisebeshownalongsidetheotherfragments,youwillneedtolaunchaseparateactivitytoshowtheuserinterfaceofthatfragment.Foryoursampleapplication,youimplementadetailsactivity;thecodeisinListing8-10.

Listing8-10.ShowingaNewActivityWhenaFragmentDoesn’tFit

publicclassDetailsActivityextendsActivity{

@OverridepublicvoidonCreate(BundlesavedInstanceState){Log.v(MainActivity.TAG,"inDetailsActivityonCreate");super.onCreate(savedInstanceState);

if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//Ifthescreenisnowinlandscapemode,itmeans//thatyourMainActivityisbeingshownwithboth

//thetitlesandthetext,sothisactivityis//nolongerneeded.BailoutandlettheMainActivity//doallthework.finish();return;}

if(getIntent()!=null){//Thisisanotherwaytoinstantiateadetails//fragment.DetailsFragmentdetails=DetailsFragment.newInstance(getIntent().getExtras());

getFragmentManager().beginTransaction().add(android.R.id.content,details).commit();}}}

Thereareseveralinterestingaspectstothiscode.Foronething,itisreallyeasytoimplement.Youmakeasimpledeterminationofthedevice’sorientation,andaslongasyou’reinportraitmode,yousetupanewdetailsfragmentwithinthisdetailsactivity.Ifyou’reinlandscapemode,yourMainActivityisabletodisplayboththetitlesfragmentandthedetailsfragment,sothereisnoreasontobedisplayingthisactivityatall.Youmaywonderwhyyouwouldeverlaunchthisactivityifyou’reinlandscapemode,andtheansweris,youwouldn’t.However,oncethisactivityhasbeenstartedinportraitmode,iftheuserrotatesthedevicetolandscapemode,thisdetailsactivitywillgetrestartedduetotheconfigurationchange.Sonowtheactivityisstartingup,andit’sinlandscapemode.Atthatmoment,itmakessensetofinishthisactivityandlettheMainActivitytakeoveranddoallthework.

AnotherinterestingaspectaboutthisdetailsactivityisthatyouneversettherootcontentviewusingsetContentView().Sohowdoestheuserinterfacegetcreated?Ifyoulookcarefullyattheadd()methodcallonthefragmenttransaction,youwillseethattheviewcontainertowhichyouaddthefragmentisspecifiedastheresourceandroid.R.id.content.Thisisthetop-levelviewcontainerforanactivity,andthereforewhenyouattachyourfragmentviewhierarchytothiscontainer,yourfragmentviewhierarchybecomestheonlyviewhierarchyfortheactivity.YouusedtheverysameDetailsFragmentclassasbeforewiththeothernewInstance()methodtocreatethefragment(theonethattakesabundleasaparameter),thenyousimplyattachedittothetopoftheactivity’sviewhierarchy.Thiscausesthefragmenttobedisplayedwithinthisnewactivity.

Fromtheuser’spointofview,theyarenowlookingatjustthedetailsfragmentview,whichisthetextfromtheShakespeareanplay.Iftheuserwantstoselectadifferenttitle,

theypresstheBackbutton,whichpopsthisactivitytorevealyourmainactivity(withthetitlesfragmentonly).Theotherchoicefortheuseristorotatethedevicetogetbacktolandscapemode.Thenyourdetailsactivitywillcallfinish()andgoaway,revealingthealso-rotatedmainactivityunderneath.

Whenthedeviceisinportraitmode,ifyou’renotshowingthedetailsfragmentinyourmainactivity,youshouldhaveaseparatemain.xmllayoutfileforportraitmodeliketheoneinListing8-11.

Listing8-11.TheLayoutforaPortraitMainActivity

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">

<fragmentclass="com.androidbook.fragments.bard.TitlesFragment"android:id="@+id/titles"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

Ofcourse,youcouldmakethislayoutwhateveryouwantittobe.Foryourpurposeshere,yousimplymakeitshowthetitlesfragmentbyitself.It’sverynicethatyourtitlesfragmentclassdoesn’tneedtoincludemuchcodetodealwiththedevicereconfiguration.

Takeamomenttoviewthisapplication’smanifestfile.InityoufindthemainactivitywithacategoryofLAUNCHERsothatitwillappearinthedevice’slistofapps.ThenyouhavetheseparateDetailsActivitywithacategoryofDEFAULT.ThisallowsyoutostartthedetailsactivityfromcodebutwillnotshowthedetailsactivityasanappintheApplist.

PersistenceofFragmentsWhenyouplaywiththissampleapplication,makesureyourotatethedevice(pressingCtrl+F11rotatesthedeviceintheemulator).Youwillseethatthedevicerotates,andthefragmentsrotaterightalongwithit.IfyouwatchtheLogCatmessages,youwillseealotofthemforthisapplication.Inparticular,duringadevicerotation,paycarefulattentiontothemessagesaboutfragments;notonlydoestheactivitygetdestroyedandre-created,butthefragmentsdoalso.

Sofar,youonlywroteatinybitofcodeonthetitlesfragmenttorememberthecurrentpositioninthetitleslistacrossrestarts.Youdidn’tdoanythinginthedetailsfragmentcodetohandlereconfigurations,andthat’sbecauseyoudidn’tneedto.Androidwilltakecareofhangingontothefragmentsthatareinthefragmentmanager,savingthemaway,

thenrestoringthemwhentheactivityisbeingre-created.Youshouldrealizethatthefragmentsyougetbackafterthereconfigurationiscompleteareverylikelynotthesamefragmentsinmemorythatyouhadbefore.Thesefragmentshavebeenreconstructedforyou.Androidsavedtheargumentsbundleandtheknowledgeofwhichtypeoffragmentitwas,anditstoredthesaved-statebundlesforeachfragmentthatcontainsaved-stateinformationaboutthefragmenttousetorestoreitontheotherside.

TheLogCatmessagesshowyouthefragmentsgoingthroughtheirlifecyclesinsyncwiththeactivity.Youwillseethatyourdetailsfragmentgetsre-created,butyournewInstance()methoddoesnotgetcalledagain.Instead,Androidusesthedefaultconstructor,attachestheargumentsbundletoit,andthenstartscallingthecallbacksonthefragment.ThisiswhyitissoimportantnottodoanythingfancyinthenewInstance()method:whenthefragmentgetsre-created,itwon’tdoitthroughnewInstance().

Youshouldalsoappreciatebynowthatyou’vebeenabletoreuseyourfragmentsinafewdifferentplaces.Thetitlesfragmentwasusedintwodifferentlayouts,butifyoulookatthetitlesfragmentcode,itdoesn’tworryabouttheattributesofeachlayout.Youcouldmakethelayoutsratherdifferentfromeachother,andthetitlesfragmentcodewouldlookthesame.Thesamecanbesaidofthedetailsfragment.Itwasusedinyourmainlandscapelayoutandwithinthedetailsactivityallbyitself.Again,thelayoutforthedetailsfragmentcouldhavebeenverydifferentbetweenthetwo,andthecodeofthedetailsfragmentwouldbethesame.Thecodeofthedetailsactivitywasverysimple,also.

Sofar,you’veexploredtwoofthefragmenttypes:thebaseFragmentclassandtheListFragmentsubclass.Fragmenthasothersubclasses:theDialogFragment,PreferenceFragment,andWebViewFragment.We’llcoverDialogFragmentandPreferenceFragmentinChapters10and11,respectively.

CommunicationswithFragmentsBecausethefragmentmanagerknowsaboutallfragmentsattachedtothecurrentactivity,theactivityoranyfragmentinthatactivitycanaskforanyotherfragmentusingthegettermethodsdescribedearlier.Oncethefragmentreferencehasbeenobtained,theactivityorfragmentcouldcastthereferenceappropriatelyandthencallmethodsdirectlyonthatactivityorfragment.Thiswouldcauseyourfragmentstohavemoreknowledgeabouttheotherfragmentsthanmightnormallybedesired,butdon’tforgetthatyou’rerunningthisapplicationonamobiledevice,socuttingcornerscansometimesbejustified.AcodesnippetisprovidedinListing8-12toshowhowonefragmentmightcommunicatedirectlywithanotherfragment.ThesnippetwouldbepartofoneofyourextendedFragmentclasses,andFragmentOtherisadifferentextendedFragmentclass.

Listing8-12.DirectFragment-to-FragmentCommunication

FragmentOtherfragOther=(FragmentOther)getFragmentManager().findFragmentByTag("other");fragOther.callCustomMethod(arg1,arg2);

InListing8-12,thecurrentfragmenthasdirectknowledgeoftheclassoftheotherfragmentandalsowhichmethodsexistonthatclass.Thismaybeokaybecausethesefragmentsarepartofoneapplication,anditcanbeeasiertosimplyacceptthefactthatsomefragmentswillknowaboutotherfragments.We’llshowyouacleanerwaytocommunicatebetweenfragmentsintheDialogFragmentsampleapplicationinChapter10.

UsingstartActivity()andsetTargetFragment()Afeatureoffragmentsthatisverymuchlikeactivitiesistheabilityofafragmenttostartanactivity.FragmenthasastartActivity()methodandstartActivityForResult()method.Theseworkjustliketheonesforactivities;whenaresultispassedback,itwillcausetheonActivityResult()callbacktofireonthefragmentthatstartedtheactivity.

There’sanothercommunicationmechanismyoushouldknowabout.Whenonefragmentwantstostartanotherfragment,thereisafeaturethatletsthecallingfragmentsetitsidentitywiththecalledfragment.Listing8-13showsanexampleofwhatitmightlooklike.

Listing8-13.Fragment-to-Target-FragmentSetup

mCalledFragment=newCalledFragment();mCalledFragment.setTargetFragment(this,0);fm.beginTransaction().add(mCalledFragment,"work").commit();

Withthesefewlines,you’vecreatedanewCalledFragmentobject,setthetargetfragmentonthecalledfragmenttothecurrentfragment,andaddedthecalledfragmenttothefragmentmanagerandactivityusingafragmenttransaction.Whenthecalledfragmentstartstorun,itwillbeabletocallgetTargetFragment(),whichwillreturnareferencetothecallingfragment.Withthisreference,thecalledfragmentcouldinvokemethodsonthecallingfragmentorevenaccessviewcomponentsdirectly.Forexample,inListing8-14,thecalledfragmentcouldsettextintheUIofthecallingfragmentdirectly.

Listing8-14.TargetFragment-to-FragmentCommunication

TextViewtv=(TextView)getTargetFragment().getView().findViewById(R.id.text1);tv.setText("Setfromthecalledfragment");

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.ThefilecalledProAndroid5_Ch08_Fragments.zipcontainsallprojects

fromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneofthesezipfiles.ItincludessomeprojectsthatusetheFragmentCompatibilitySDKforolderAndroidsaswell.

http://developer.android.com/guide/components/fragments.htmlTheAndroidDeveloper’sGuidepagetofragments.

http://developer.android.com/design/patterns/multi-pane-layouts.html:Androiddesignguidelinesformultipanelayouts.

http://developer.android.com/training/basics/fragments/index.htmlAndroidtrainingpageforfragments.

SummaryThischapterintroducedtheFragmentclassanditsrelatedclassesforthemanager,transactions,andsubclasses.Thisisasummaryofwhat’sbeencoveredinthischapter:

TheFragmentclass,whatitdoes,andhowtouseit.

Whyfragmentscannotbeusedwithoutbeingattachedtooneandonlyoneactivity.

ThatalthoughfragmentscanbeinstantiatedwithastaticfactorymethodsuchasnewInstance(),youmustalwayshaveadefaultconstructorandawaytosaveinitializationvaluesintoaninitializationargumentsbundle.

Thelifecycleofafragmentandhowitisintertwinedwiththelifecycleoftheactivitythatownsthefragment.

FragmentManageranditsfeatures.

Managingdeviceconfigurationsusingfragments.

Combiningfragmentsintoasingleactivity,orsplittingthembetweenmultipleactivities.

Usingfragmenttransactionstochangewhat’sdisplayedtoauser,andanimatingthosetransitionsusingcooleffects.

NewbehaviorsthatarepossiblewiththeBackbuttonwhenusingfragments.

Usingthe<fragment>taginalayout.

UsingaFrameLayoutasaplaceholderforafragmentwhenyouwanttousetransitions.

ListFragmentandhowtouseanadaptertopopulatethedata

(verymuchlikeaListView).

Launchinganewactivitywhenafragmentcan’tfitontothecurrentscreen,andhowtoadjustwhenaconfigurationchangemakesitpossibletoseemultiplefragmentsagain.

Communicatingbetweenfragments,andbetweenafragmentanditsactivity.

Chapter9

RespondingtoConfigurationChangesWe’vecoveredafairbitofmaterialsofar,andnowseemslikeagoodtimetocoverconfigurationchanges.Whenanapplicationisrunningonadevice,andthedevice’sconfigurationchanges(forexample,isrotated90degrees),yourapplicationneedstorespondaccordingly.Thenewconfigurationwillmostlikelylookdifferentfromthepreviousconfiguration.Forexample,switchingfromportraittolandscapemodemeansthescreenwentfrombeingtallandnarrowtobeingshortandwide.TheUIelements(buttons,text,lists,andsoon)willneedtoberearranged,resized,orevenremovedtoaccommodatethenewconfiguration.

InAndroid,aconfigurationchangebydefaultcausesthecurrentactivitytogoawayandbere-created.Theapplicationitselfkeepsonrunning,butithastheopportunitytochangehowtheactivityisdisplayedinresponsetotheconfigurationchange.Intherarecasethatyouneedtohandleaconfigurationchangewithoutdestroyingandre-creatingyouractivity,Androidprovidesawaytohandlethataswell.

Beawarethatconfigurationchangescantakeonmanyforms,notjustdevicerotation.Ifadevicegetsconnectedtoadock,that’salsoaconfigurationchange.Soischangingthelanguageofthedevice.Whateverthenewconfigurationis,aslongasyou’vedesignedyouractivityforthatconfiguration,Androidtakescareofmosteverythingtotransitiontoit,givingtheuseraseamlessexperience.

Thischapterwilltakeyouthroughtheprocessofaconfigurationchange,fromtheperspectivesofbothactivitiesandfragments.We’llshowyouhowtodesignyourapplicationforthosetransitionsandhowtoavoidtrapsthatcouldcauseyourapplicationtocrashormisbehave.

TheDefaultConfigurationChangeProcessTheAndroidoperatingsystemkeepstrackofthecurrentconfigurationofthedeviceit’srunningon.Configurationincludeslotsoffactors,andnewonesgetaddedallthetime.Forexample,ifadeviceispluggedintoadockingstation,thatrepresentsachangeinthedeviceconfiguration.WhenaconfigurationchangeisdetectedbyAndroid,callbacksareinvokedinrunningapplicationstotellthemachangeisoccurring,soanapplicationcanproperlyrespondtothechange.We’lldiscussthosecallbacksalittlelater,butfornowlet’srefreshyourmemorywithregardtoresources.

OneofthegreatfeaturesofAndroidisthatresourcesgetselectedforyouractivitybasedonthecurrentconfigurationofthedevice.Youdon’tneedtowritecodetofigureoutwhichconfigurationisactive;youjustaccessresourcesbyname,andAndroidgetstheappropriateresourcesforyou.Ifthedeviceisinportraitmodeandyourapplication

requestsalayout,yougettheportraitlayout.Ifthedeviceisinlandscapemode,yougetthelandscapelayout.Thecodejustrequestsalayoutwithoutspecifyingwhichoneitshouldget.Thisispowerfulbecauseasnewconfigurationfactorsgetintroduced,ornewvaluesforconfigurationfactors,thecodestaysthesame.Alladeveloperneedstodoisdecideifnewresourcesneedtobecreated,andcreatethemasnecessaryforthenewconfiguration.Then,whentheapplicationgoesthroughaconfigurationchange,Androidprovidesthenewresourcestotheapplication,andeverythingcontinuestofunctionasdesired.

Becauseofagreatdesiretokeepthingssimple,Androiddestroysthecurrentactivitywhentheconfigurationchangesandcreatesanewoneinitsplace.Thismightseemratherharsh,butit’snot.Itisabiggerchallengetotakearunningactivityandfigureoutwhichpartswouldstaythesameandwhichwouldnot,andthenonlyworkwiththepiecesthatneedtochange.

Anactivitythat’sabouttobedestroyedisproperlynotifiedfirst,givingyouachancetosaveanythingthatneedstobesaved.Whenthenewactivitygetscreated,ithastheopportunitytorestorestateusingdatafromthepreviousactivity.Foragooduserexperience,obviouslyyoudonotwantthissaveandrestoretotakeverylong.

It’sfairlyeasytosaveanydatathatyouneedsavedandthenletAndroidthrowawaytherestandstartover,aslongasthedesignoftheapplicationanditsactivitiesissuchthatactivitiesdon’tcontainalotofnon-UIstuffthatwouldtakealongtimetore-create.Thereinliesthesecrettosuccessfulconfigurationchangedesign:donotput“stuff”insideanactivitythatcannotbeeasilyre-createdduringaconfigurationchange.

Keepinmindthatourapplicationisnotbeingdestroyed,soanythingthatisintheapplicationcontext,andnotapartofourcurrentactivity,willstillbethereforthenewactivity.Singletonswillstillbeavailable,aswellasanybackgroundthreadswemighthavespunofftodoworkforourapplication.Anydatabasesorcontentprovidersthatwewereworkingwithwillalsostillbearound.Takingadvantageofthesemakesconfigurationchangesquickandpainless.Keepdataandbusinesslogicoutsideofactivitiesifyoucan.

Theconfigurationchangeprocessissomewhatsimilarbetweenactivitiesandfragments.Whenanactivityisbeingdestroyedandre-created,thefragmentswithinthatactivitygetdestroyedandre-created.Whatweneedtoworryaboutthenisstateinformationaboutourfragmentsandactivity,suchasdatacurrentlybeingdisplayedtotheuser,orinternalvaluesthatwewanttopreserve.Wewillsavewhatwewanttokeep,andpickitupagainontheothersidewhenthefragmentsandactivitiesarebeingre-created.You’llwanttoprotectdatathatcan’teasilybere-createdbynotlettingitgetdestroyedinthedefaultconfigurationchangeprocess.

TheDestroy/CreateCycleofActivitiesTherearethreecallbackstobeawareofwhendealingwithdefaultconfigurationchangesinactivities:

onSaveInstanceState()

onCreate()

onRestoreInstanceState()

ThefirstisthecallbackthatAndroidwillinvokewhenitdetectsthataconfigurationchangeishappening.Theactivityhasachancetosavestatethatitwantstorestorewhenthenewactivitygetscreatedattheendoftheconfigurationchange.TheonSaveInstanceState()callbackwillbecalledpriortothecalltoonStop().WhateverstateexistscanbeaccessedandsavedintoaBundleobject.Thisobjectwillgetpassedintobothoftheothercallbacks(onCreate()andonRestoreInstanceState())whentheactivityisre-created.Youonlyneedtoputlogicinoneortheothertorestoreyouractivity’sstate.

ThedefaultonSaveInstanceState()callbackdoessomenicethingsforyou.Forexample,itgoesthroughthecurrentlyactiveviewhierarchyandsavesthevaluesforeachviewthathasanandroid:id.ThismeansifyouhaveanEditTextviewthathasreceivedsomeuserinput,thatinputwillbeavailableontheothersideoftheactivitydestroy/createcycletopopulatetheEditTextbeforetheusergetscontrolback.Youdonotneedtogothroughandsavethisstateyourself.IfyoudooverrideonSaveInstanceState(),besuretocallsuper.onSaveInstanceState()withthebundleobjectsoitcantakecareofthisforyou.It’snottheviewsthataresaved,onlytheattributesoftheirstatethatshouldpersistacrossthedestroy/createboundary.

Tosavedatainthebundleobject,usemethodssuchasputInt()forintegersandputString()forstrings.Therearequiteafewmethodsintheandroid.os.Bundleclass;youarenotlimitedtointegersandstrings.Forexample,putParcelable()canbeusedtosavecomplexobjects.Eachputisusedwithastringkey,andyouwillretrievethevaluelaterusingthesamekeyusedtoputthevaluein.AsampleonSaveInstanceState()mightlooklikeListing9-1.

Listing9-1.SampleonSaveInstanceState()

@OverridepublicvoidonSaveInstanceState(Bundleicicle){super.onSaveInstanceState(icicle);icicle.putInt("counter",1);}

Sometimesthebundleiscallediciclebecauseitrepresentsasmallfrozenpieceofanactivity.Inthissample,youonlysaveonevalue,andithasakeyofcounter.Youcouldsavemorevaluesbysimplyaddingmoreputstatementstothiscallback.Thecountervalueinthisexampleissomewhattemporarybecauseiftheapplicationiscompletelydestroyed,thecurrentvaluewillbelost.Thiscouldhappeniftheuserturnedofftheirdevice,forexample.InChapter11,you’lllearnaboutwaystosavevaluesmorepermanently.Thisinstancestateisonlymeanttohangontovalueswhiletheapplicationisrunningthistime.Donotusethismechanismforstatethatisimportanttokeepforalongerterm.

Torestoreactivitystate,youaccessthebundleobjecttoretrievevaluesthatyoubelievearethere.Again,youusemethodsoftheBundleclasssuchasgetInt()andgetString()withtheappropriatekeypassedtotellwhichvalueyouwantback.IfthekeydoesnotexistintheBundle,avalueof0ornullispassedback(dependingonthetypeoftheobjectbeingrequested).Oryoucanprovideadefaultvalueintheappropriategettermethod.Listing9-2showsasampleonRestoreInstanceState()callback.

Listing9-2.SampleonRestoreInstanceState()

@OverridepublicvoidonRestoreInstanceState(Bundleicicle){super.onRestoreInstanceState(icicle);intsomeInt=icicle.getInt("counter",-1);//NowgodosomethingwithsomeInttorestorethe//stateoftheactivity.-1isthedefaultifno//valuewasfound.}

It’suptoyouwhetheryourestorestateinonCreate()orinonRestoreInstanceState().ManyapplicationswillrestorestateinonCreate()becausethatiswherealotofinitializationisdone.Onereasontoseparatethetwowouldbeifyou’recreatinganactivityclassthatcouldbeextended.ThedevelopersdoingtheextendingmightfinditeasiertojustoverrideonRestoreInstanceState()withthecodetorestorestate,ascomparedtohavingtooverrideallofonCreate().

What’sveryimportanttonotehereisthatyouneedtobeveryconcernedwithreferencestoactivitiesandviewsandotherobjectsthatneedtobegarbage-collectedwhenthecurrentactivityisfullydestroyed.Ifyouputsomethingintothesavedbundlethatrefersbacktotheactivitybeingdestroyed,thatactivitycan’tbegarbagecollected.Thisisverylikelyamemoryleakthatcouldgrowandgrowuntilyourapplicationcrashes.ObjectstoavoidinbundlesincludeDrawables,Adapters,Views,andanythingelsethatistiedtotheactivitycontext.InsteadofputtingaDrawableintothebundle,serializethebitmapandsavethat.Orbetteryet,managethebitmapsoutsideoftheactivityandfragmentinsteadofinside.Addsomesortofreferencetothebitmaptothebundle.Whenitcomestimetore-createanyDrawablesforthenewfragment,usethereferencetoaccesstheoutsidebitmapstoregenerateyourDrawables.

TheDestroy/CreateCycleofFragmentsThedestroy/createcycleforfragmentsisverysimilartothatofactivities.Afragmentintheprocessofbeingdestroyedandre-createdwillhaveitsonSaveInstanceState()callbackcalled,allowingthefragmenttosavevaluesinaBundleobjectforlater.OnedifferenceisthatsixfragmentcallbacksreceivethisBundleobjectwhenafragmentisbeingre-created:onInflate(),onCreate(),onCreateView(),onActivityCreated(),onViewCreated(),andonViewStateRestored().

Thelasttwocallbacksaremorerecent,fromHoneycomb3.2andJellyBean4.2respectively.Thisgivesuslotsofopportunitiestorebuildtheinternalstateofourreconstructedfragmentfromitspreviousstate.

AndroidguaranteesonlythatonSaveInstanceState()willbecalledforafragmentsometimebeforeonDestroy().ThatmeanstheviewhierarchymayormaynotbeattachedwhenonSaveInstanceState()iscalled.Therefore,don’tcountontraversingtheviewhierarchyinsideofonSaveInstanceState().Forexample,ifthefragmentisonthefragmentbackstack,noUIwillbeshowing,sonoviewhierarchywillexist.ThisisOKofcoursebecauseifnoUIisshowing,thereisnoneedtoattempttocapturethecurrentvaluesofviewstosavethem.Youneedtocheckifaviewexistsbeforetryingtosaveitscurrentvalue,andnotconsideritanerroriftheviewdoesnotexist.

Justlikewithactivities,becarefulnottoincludeitemsinthebundleobjectthatrefertoanactivityortoafragmentthatmightnotexistlaterwhenthisfragmentisbeingre-created.Keepthesizeofthebundleassmallaspossible,andasmuchaspossiblestorelong-lastingdataoutsideofactivitiesandfragmentsandsimplyrefertoitfromyouractivitiesandfragments.Thenyourdestroy/createcycleswillgothatmuchfaster,you’llbemuchlesslikelytocreateamemoryleak,andyouractivityandfragmentcodeshouldbeeasiertomaintain.

UsingFragmentManagertoSaveFragmentStateFragmentshaveanotherwaytosavestate,inadditionto,orinsteadof,Androidnotifyingthefragmentsthattheirstateshouldbesaved.WithHoneycomb3.2,theFragmentManagerclassgotasaveFragmentInstanceState()methodthatcanbecalledtogenerateanobjectoftheclassFragment.SavedState.ThemethodsmentionedintheprevioussectionsforsavingstatedosowithintheinternalsofAndroid.Whileweknowthatthestateisbeingsaved,wedonothaveanydirectaccesstoit.Thismethodofsavingstategivesyouanobjectthatrepresentsthesavedstateofafragmentandallowsyoutocontrolifandwhenafragmentiscreatedfromthatstate.

ThewaytouseaFragment.SavedStateobjecttorestoreafragmentisthroughthesetInitialSavedState()methodoftheFragmentclass.InChapter8,youlearnedthatitisbesttocreatenewfragmentsusingastaticfactorymethod(forexample,newInstance()).Withinthismethod,yousawhowadefaultconstructoriscalledandthenanargumentsbundleisattached.YoucouldinsteadcallthesetInitialSavedState()methodtosetitupforrestorationtoapreviousstate.

Thereareafewcaveatsyoushouldknowaboutthismethodofsavingfragmentstate:

Thefragmenttobesavedmustcurrentlybeattachedtothefragmentmanager.

Anewfragmentcreatedusingthissavedstatemustbethesameclasstypeasthefragmentitwascreatedfrom.

Thesavedstatecannotcontaindependenciesonotherfragments.Otherfragmentsmaynotexistwhenthesavedfragmentisre-created.

UsingsetRetainInstanceonaFragmentAfragmentcanavoidbeingdestroyedandre-createdonaconfigurationchange.IfthesetRetainInstance()methodiscalledwithanargumentoftrue,thefragmentwillberetainedintheapplicationwhenitsactivityisbeingdestroyedandre-created.Thefragment’sonDestroy()callbackwillnotbecalled,norwillonCreate().TheonDetach()callbackwillbecalledbecausethefragmentmustbedetachedfromtheactivitythat’sgoingaway,andonAttach()andonActivityCreated()willbecalledbecausethefragmentisattachedtoanewactivity.Thisonlyworksforfragmentsthatarenotonthebackstack.ItisespeciallyusefulforfragmentsthatdonothaveaUI.

Thisfeatureisverypowerfulinthatyoucanuseanon-UIfragmenttohandlereferencestoyourdataobjectsandbackgroundthreads,andcallsetRetainInstance(true)onthisfragmentsoitwon’tgetdestroyedandre-createdonaconfigurationchange.Theaddedbonusisthatduringthenormalconfigurationchangeprocess,thenon-UIfragmentcallbacksonDetach()andonAttach()willswitchtheactivityreferencefromtheoldtothenew.

DeprecatedConfigurationChangeMethodsAcoupleofmethodsonActivityhavebeendeprecated,soyoushouldnolongerusethem:

getLastNonConfigurationInstance()

onRetainNonConfigurationInstance()

Thesemethodspreviouslyallowedyoutosaveanarbitraryobjectfromanactivitythatwasbeingdestroyed,tobepassedtothenextinstanceoftheactivitythatwasbeingcreated.Althoughtheywereuseful,youshouldnowusethemethodsdescribedearlierinsteadtomanagedatabetweeninstancesofactivitiesinthedestroy/createcycle.

HandlingConfigurationChangesYourselfSofar,you’veseenhowAndroidhandlesconfigurationchangesforyou.Ittakescareofdestroyingandre-creatingactivitiesandfragments,pullinginthebestresourcesforthenewconfiguration,retaininganyuser-entereddata,andgivingyoutheopportunitytoexecutesomeextralogicinsomecallbacks.Thisisusuallygoingtobeyourbestoption.Butwhenitisn’t,whenyouhavetohandleaconfigurationchangeyourself,Androidprovidesawayout.Thisisn’trecommendedbecauseitisthencompletelyuptoyoutodeterminewhatneedstochangeduetothechange,andthenforyoutotakecareofmakingallthechanges.Asmentionedbefore,therearemanyconfigurationchangesbesidesjustanorientationchange.Luckily,youdon’tnecessarilyhavetohandleall

configurationchangesyourself.

Thefirststeptohandlingconfigurationchangesyourselfistodeclareinthe<activity>taginAndroidManifest.xmlfilewhichchangesyou’regoingtohandleusingtheandroid:configChangesattribute.Androidwillhandletheotherconfigurationchangesusingthepreviouslydescribedmethods.Youcanspecifyasmanyconfigurationchangetypesasneededbyor’ingthemtogetherwiththe‘|’symbol,likethis:

<activity...android:configChanges="orientation|keyboardHidden"...>

ThecompletelistofconfigurationchangetypescanbefoundonthereferencepageforR.attr.BeawarethatifyoutargetAPI13orhigherandyouneedtohandleorientation,youalsoneedtohandlescreenSize.

Thedefaultprocessforaconfigurationchangeistheinvokingofcallbackstodestroyandre-createtheactivityorfragment.Whenyou’vedeclaredthatyouwillhandlethespecificconfigurationchange,theprocesschangessoonlytheonConfigurationChanged()callbackisinvokedinstead,ontheactivityanditsfragments.AndroidpassesinaConfigurationobjectsothecallbackknowswhatthenewconfigurationis.Itisuptothecallbacktodeterminewhatmighthavechanged;however,sinceyoulikelyhandleonlyasmallnumberofconfigurationchangesyourself,itshouldn’tbetoohardtofigurethisout.

You’dreallyonlywanttohandleaconfigurationchangeyourselfwhenthereisverylittletobedone,whenyoucouldskipdestroyingandre-creating.Forexample,iftheactivitylayoutforportraitandlandscapeisthesamelayoutandallimageresourcesarethesame,destroyingandre-creatingtheactivitydoesn’treallyaccomplishanything.Inthiscaseitwouldbefairlysafetodeclarethatyouwillhandletheorientationconfigurationchange.Duringanorientationchangeofyouractivity,theactivitywouldremainintactandsimplyre-renderitselfintheneworientationusingtheexistingresourcessuchasthelayout,images,strings,etc.Butit’sreallynotthatbigadealtojustletAndroidtakecareofthingsifyoucan.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch09_ConfigChanges.zip.ThisZIPfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.

http://developer.android.com/guide/topics/fundamentals/activities.html#SavingActivityStateTheAndroidDeveloper’sGuide,whichdiscussessavingand

restoringstate.

http://developer.android.com/guide/topics/resources/runtime-changes.html:TheAndroidAPIGuideforHandlingRuntimeChanges.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouthandlingconfigurationchanges:

Activitiesbydefaultgetdestroyedandre-createdduringconfigurationchanges.Sodofragments.

Avoidputtinglotsofdataandlogicintoactivitiessoconfigurationchangesoccurquickly.

LetAndroidprovidetheappropriateresources.

Usesingletonstoholddataoutsideofactivitiestomakeiteasiertodestroyandre-createactivitiesduringconfigurationchanges.

TakeadvantageofthedefaultonSaveInstanceState()callbacktosaveUIstateonviewswithandroid:ids.

Ifafragmentcansurvivewithnoissuesacrossanactivitydestroy-and-createcycle,usesetRetainInstance()totellAndroiditdoesn’tneedtodestroyandcreatethefragment.

Chapter10

WorkingwithDialogsTheAndroidSDKoffersextensivesupportfordialogs.Adialogisasmallerwindowthatpopsupinfrontofthecurrentwindowtoshowanurgentmessage,toprompttheuserforapieceofinput,ortoshowsomesortofstatusliketheprogressofadownload.Theuserisgenerallyexpectedtointeractwiththedialogandthenreturntothewindowunderneathtocontinuewiththeapplication.Technically,Androidallowsadialogfragmenttoalsobeembeddedwithinanactivity’slayout,andwe’llcoverthataswell.

DialogsthatareexplicitlysupportedinAndroidincludethealert,prompt,pick-list,single-choice,multiple-choice,progress,time-picker,anddate-pickerdialogs.(ThislistcouldvarydependingontheAndroidrelease.)Androidalsosupportscustomdialogsforotherneeds.TheprimarypurposeofthischapterisnottocovereverysingleoneofthesedialogsbuttocovertheunderlyingarchitectureofAndroiddialogswithasampleapplication.FromthereyoushouldbeabletouseanyoftheAndroiddialogs.

It’simportanttonotethatAndroid3.0addeddialogsbasedonfragments.TheexpectationfromGoogleisthatdeveloperswillonlyusefragmentdialogs,evenintheversionsofAndroidbefore3.0.Thiscanbedonewiththefragment-compatibilitylibrary.Forthisreason,thischapterfocusesonDialogFragment.

UsingDialogsinAndroidDialogsinAndroidareasynchronous,whichprovidesflexibility.However,ifyouareaccustomedtoaprogrammingframeworkwheredialogsareprimarilysynchronous(suchasMicrosoftWindows,orJavaScriptdialogsinwebpages),youmightfindasynchronousdialogsabitunintuitive.Withasynchronousdialog,thelineofcodeafterthedialogisshowndoesnotrununtilthedialoghasbeendismissed.Thismeansthenextlineofcodecouldinterrogatewhichbuttonwaspressed,orwhattextwastypedintothedialog.InAndroidhowever,dialogsareasynchronous.Assoonasthedialoghasbeenshown,thenextlineofcoderuns,eventhoughtheuserhasn’ttouchedthedialogyet.Yourapplicationhastodealwiththisfactbyimplementingcallbacksfromthedialog,toallowtheapplicationtobenotifiedofuserinteractionwiththedialog.

Thisalsomeansyourapplicationhastheabilitytodismissthedialogfromcode,whichispowerful.Ifthedialogisdisplayingabusymessagebecauseyourapplicationisdoingsomething,assoonasyourapplicationhascompletedthattask,itcandismissthedialogfromcode.

UnderstandingDialogFragmentsInthissection,youlearnhowtousedialogfragmentstopresentasimplealertdialogand

acustomdialogthatisusedtocollectprompttext.

DialogFragmentBasicsBeforeweshowyouworkingexamplesofapromptdialogandanalertdialog,wewouldliketocoverthehigh-levelideaofdialogfragments.Dialog-relatedfunctionalityusesaclasscalledDialogFragment.ADialogFragmentisderivedfromtheclassFragmentandbehavesmuchlikeafragment.YouwillthenusetheDialogFragmentasthebaseclassforyourdialogs.Onceyouhaveaderiveddialogfromthisclasssuchas

publicclassMyDialogFragmentextendsDialogFragment{...}

youcanthenshowthisdialogfragmentMyDialogFragmentasadialogusingafragmenttransaction.Listing10-1showsacodesnippettodothis.

Listing10-1.ShowingaDialogFragment

publicclassSomeActivityextendsActivity{//....otheractivityfunctionspublicvoidshowDialog(){//constructMyDialogFragmentMyDialogFragmentmdf=MyDialogFragment.newInstance(arg1,arg2);FragmentManagerfm=getFragmentManager();FragmentTransactionft=fm.beginTransaction();mdf.show(ft,"my-dialog-tag");}//....otheractivityfunctions}

NoteWeprovidealinktoadownloadableprojectattheendofthischapterinthe“References”section.Youcanusethisdownloadtoexperimentwiththecodeandtheconceptspresentedinthischapter.

FromListing10-1,thestepstoshowadialogfragmentareasfollows:

1. Createadialogfragment.

2. Getafragmenttransaction.

3. Showthedialogusingthefragmenttransactionfromstep2.

Let’stalkabouteachofthesesteps.

ConstructingaDialogFragmentWhenconstructingadialogfragment,therulesarethesameaswhenbuildinganyother

kindoffragment.TherecommendedpatternistouseafactorymethodsuchasnewInstance()asyoudidbefore.InsidethatnewInstance()method,youusethedefaultconstructorforyourdialogfragment,andthenyouaddanargumentsbundlethatcontainsyourpassed-inparameters.Youdon’twanttodootherworkinsidethismethodbecauseyoumustmakesurethatwhatyoudohereisthesameaswhatAndroiddoeswhenitrestoresyourdialogfragmentfromasavedstate.AndallthatAndroiddoesistocallthedefaultconstructorandre-createtheargumentsbundleonit.

OverridingonCreateViewWhenyouinheritfromadialogfragment,youneedtooverrideoneoftwomethodstoprovidetheviewhierarchyforyourdialog.ThefirstoptionistooverrideonCreateView()andreturnaview.ThesecondoptionistooverrideonCreateDialog()andreturnadialog(liketheoneconstructedbyanAlertDialog.Builder,whichwe’llgettoshortly).

Listing10-2showsanexampleofoverridingtheonCreateView().

Listing10-2.OverridingonCreateView()ofaDialogFragment

publicclassMyDialogFragmentextendsDialogFragmentimplementsView.OnClickListener{.....otherfunctionspublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){//CreateaviewbyinflatingdesiredlayoutViewv=inflater.inflate(R.layout.prompt_dialog,container,false);

//youcanlocateaviewandsetvaluesTextViewtv=(TextView)v.findViewById(R.id.promptmessage);tv.setText(this.getPrompt());

//YoucansetcallbacksonbuttonsButtondismissBtn=(Button)v.findViewById(R.id.btn_dismiss);dismissBtn.setOnClickListener(this);

ButtonsaveBtn=(Button)v.findViewById(R.id.btn_save);saveBtn.setOnClickListener(this);returnv;}.....otherfunctions}

InListing10-2,youareloadingaviewidentifiedbyalayout.Thenyoulookfortwobuttonsandsetupcallbacksonthem.ThisisverysimilartohowyoucreatedthedetailsfragmentinChapter8.However,unliketheearlierfragments,adialogfragmenthasanotherwaytocreatetheviewhierarchy.

OverridingonCreateDialogAsanalternatetosupplyingaviewinonCreateView(),youcanoverrideonCreateDialog()andsupplyadialoginstance.Listing10-3suppliessamplecodeforthisapproach.

Listing10-3.OverridingonCreateDialog()ofaDialogFragment

publicclassMyDialogFragmentextendsDialogFragmentimplementsDialogInterface.OnClickListener{.....otherfunctions@OverridepublicDialogonCreateDialog(Bundleicicle){AlertDialog.Builderb=newAlertDialog.Builder(getActivity()).setTitle("MyDialogTitle").setPositiveButton("Ok",this).setNegativeButton("Cancel",this).setMessage(this.getMessage());returnb.create();}.....otherfunctions}

Inthisexample,youusethealertdialogbuildertocreateadialogobjecttoreturn.Thisworkswellforsimpledialogs.ThefirstoptionofoverridingonCreateView()isequallyeasyandprovidesmuchmoreflexibility.

AlertDialog.Builderisactuallyacarryoverfrompre-3.0Android.Thisisoneoftheoldwaystocreateadialog,andit’sstillavailabletoyoutocreatedialogswithinDialogFragments.Asyoucansee,it’sfairlyeasytobuildadialogbycallingthevariousmethodsavailable,aswe’vedonehere.

DisplayingaDialogFragmentOnceyouhaveadialogfragmentconstructed,youneedafragmenttransactiontoshowit.Likeallotherfragments,operationsondialogfragmentsareconductedthroughfragmenttransactions.

Theshow()methodonadialogfragmenttakesafragmenttransactionasaninput.YoucanseethisinListing10-1.Theshow()methodusesthefragmenttransactiontoaddthisdialogtotheactivityandthencommitsthefragmenttransaction.However,theshow()

methoddoesnotaddthetransactiontothebackstack.Ifyouwanttodothis,youneedtoaddthistransactiontothebackstackfirstandthenpassittotheshow()method.Theshow()methodofadialogfragmenthasthefollowingsignatures:

publicintshow(FragmentTransactiontransaction,Stringtag)publicintshow(FragmentManagermanager,Stringtag)

Thefirstshow()methoddisplaysthedialogbyaddingthisfragmenttothepassed-intransactionwiththespecifiedtag.Thismethodthenreturnstheidentifierofthecommittedtransaction.

Thesecondshow()methodautomatesgettingatransactionfromthetransactionmanager.Thisisashortcutmethod.However,whenyouusethissecondmethod,youdon’thaveanoptiontoaddthetransactiontothebackstack.Ifyouwantthatcontrol,youneedtousethefirstmethod.Thesecondmethodcouldbeusedifyouwantedtosimplydisplaythedialog,andyouhadnootherreasontoworkwithafragmenttransactionatthattime.

Anicethingaboutadialogbeingafragmentisthattheunderlyingfragmentmanagerdoesthebasicstatemanagement.Forexample,evenifthedevicerotateswhenadialogisbeingdisplayed,thedialogisreproducedwithoutyouperforminganystatemanagement.

Thedialogfragmentalsooffersmethodstocontroltheframeinwhichthedialog’sviewisdisplayed,suchasthetitleandtheappearanceoftheframe.RefertotheDialogFragmentclassdocumentationtoseemoreoftheseoptions;thisURLisprovidedattheendofthischapter.

DismissingaDialogFragmentTherearetwowaysyoucandismissadialogfragment.Thefirstistoexplicitlycallthedismiss()methodonthedialogfragmentinresponsetoabuttonorsomeactiononthedialogview,asshowninListing10-4.

Listing10-4.Callingdismiss()

if(someview.getId()==R.id.btn_dismiss){//usesomecallbackstoadviseclients//ofthisdialogthatitisbeingdismissed//andcalldismissdismiss();return;}

Thedialogfragment’sdismiss()methodremovesthefragmentfromthefragmentmanagerandthencommitsthattransaction.Ifthereisabackstackforthisdialogfragment,thenthedismiss()popsthecurrentdialogoutofthetransactionstackandpresentsthepreviousfragmenttransactionstate.Whetherthereisabackstackornot,callingdismiss()resultsincallingthestandarddialogfragmentdestroycallbacks,

includingonDismiss().

Onethingtonoteisthatyoucan’trelyononDismiss()toconcludethatadismiss()hasbeencalledbyyourcode.ThisisbecauseonDismiss()isalsocalledwhenadeviceconfigurationchangesandhenceisnotagoodindicatorofwhattheuserdidtothedialogitself.Ifthedialogisbeingdisplayedwhentheuserrotatesthedevice,thedialogfragmentseesonDismiss()calledeventhoughtheuserdidnotpressabuttoninthedialog.Instead,youshouldalwaysrelyonexplicitbuttonclicksonthedialogview.

IftheuserpressestheBackbuttonwhilethedialogfragmentisdisplayed,thiscausestheonCancel()callbacktofireonthedialogfragment.Bydefault,Androidmakesthedialogfragmentgoaway,soyoudon’tneedtocalldismiss()onthefragmentyourself.Butifyouwantthecallingactivitytobenotifiedthatthedialoghasbeencancelled,youneedtoinvokelogicfromwithinonCancel()tomakethathappen.ThisisadifferencebetweenonCancel()andonDismiss()withdialogfragments.WithonDismiss(),youcan’tbesureexactlywhathappenedthatcausedtheonDismiss()callbacktofire.Youmightalsohavenoticedthatadialogfragmentdoesnothaveacancel()method,justdismiss();butaswesaid,whenadialogfragmentisbeingcancelledbypressingtheBackbutton,Androidtakescareofcancelling/dismissingitforyou.

Theotherwaytodismissadialogfragmentistopresentanotherdialogfragment.Thewayyoudismissthecurrentdialogandpresentthenewoneisslightlydifferentthanjustdismissingthecurrentdialog.Listing10-5showsanexample.

Listing10-5.SettingUpaDialogforaBackStack

if(someview.getId()==R.id.btn_invoke_another_dialog){Activityact=getActivity();FragmentManagerfm=act.getFragmentManager();FragmentTransactionft=fm.beginTransaction();ft.remove(this);

ft.addToBackStack(null);//nullrepresentsnonameforthebackstacktransaction

HelpDialogFragmenthdf=HelpDialogFragment.newInstance(R.string.helptext);hdf.show(ft,"HELP");return;}

Withinasingletransaction,you’reremovingthecurrentdialogfragmentandaddingthenewdialogfragment.Thishastheeffectofmakingthecurrentdialogdisappearvisuallyandmakingthenewdialogappear.IftheuserpressestheBackbutton,becauseyou’vesavedthistransactiononthebackstack,thenewdialogisdismissedandtheprevious

dialogisdisplayed.Thisisahandywayofdisplayingahelpdialog,forexample.

ImplicationsofaDialogDismissWhenyouaddanyfragmenttoafragmentmanager,thefragmentmanagerdoesthestatemanagementforthatfragment.Thismeanswhenadeviceconfigurationchanges(forexample,thedevicerotates),theactivityisrestartedandthefragmentsarealsorestarted.YousawthisearlierwhenyourotatedthedevicewhilerunningtheShakespearesampleapplicationinchapter8.

Adevice-configurationchangedoesn’taffectdialogsbecausetheyarealsomanagedbythefragmentmanager.Buttheimplicitbehaviorofshow()anddismiss()meansyoucaneasilylosetrackofadialogfragmentifyou’renotcareful.Theshow()methodautomaticallyaddsthefragmenttothefragmentmanager;thedismiss()methodautomaticallyremovesthefragmentfromthefragmentmanager.Youmayhaveadirectpointertoadialogfragmentbeforeyoustartshowingthefragment.Butyoucan’taddthisfragmenttothefragmentmanagerandlatercallshow(),becauseafragmentcanonlybeaddedoncetothefragmentmanager.Youmayplantoretrievethispointerthroughrestoreoftheactivity.However,ifyoushowanddismissthisdialog,thisfragmentisimplicitlyremovedfromthefragmentmanager,therebydenyingthatfragment’sabilitytoberestoredandrepointed(becausethefragmentmanagerdoesn’tknowthisfragmentexistsafteritisremoved).

Ifyouwanttokeepthestateofadialogafteritisdismissed,youneedtomaintainthestateoutsideofthedialogeitherintheparentactivityorinanon-dialogfragmentthathangsaroundforalongertime.

DialogFragmentSampleApplicationInthissection,youreviewasampleapplicationthatdemonstratestheseconceptsofadialogfragment.Youalsoexaminecommunicationbetweenafragmentandtheactivitythatcontainsit.Tomakeitallhappen,youneedfiveJavafiles:

MainActivity.java:Themainactivityofyourapplication.Itdisplaysasimpleviewwithhelptextinitandamenufromwhichdialogscanbestarted.

PromptDialogFragment.java:AnexampleofadialogfragmentthatdefinesitsownlayoutinXMLandallowsinputfromtheuser.Ithasthreebuttons:Save,Dismiss(cancel),andHelp.

AlertDialogFragment.java:AnexampleofadialogfragmentthatusestheAlertBuilderclasstocreateadialogwithinthisfragment.Thisistheold-schoolwayofcreatingadialog.

HelpDialogFragment.java:Averysimplefragmentthatdisplaysahelpmessagefromtheapplication’sresources.Thespecifichelpmessageisidentifiedwhenahelpdialogobjectis

created.Thishelpfragmentcanbeshownfromboththemainactivityandthepromptdialogfragment.

OnDialogDoneListener.java:Aninterfacethatyourequireyouractivitytoimplementinordertogetmessagesbackfromthefragments.Usinganinterfacemeansyourfragmentsdon’tneedtoknowmuchaboutthecallingactivity,exceptthatitmusthaveimplementedthisinterface.Thishelpsencapsulatefunctionalitywhereitbelongs.Fromtheactivity’spointofview,ithasacommonwaytoreceiveinformationbackfromfragmentswithoutneedingtoknowtoomuchaboutthem.

Therearethreelayoutsforthisapplication:forthemainactivity,forthepromptdialogfragment,andforthehelpdialogfragment.Notethatyoudon’tneedalayoutforthealertdialogfragmentbecausetheAlertBuildertakescareofthatlayoutforyouinternally.Whenyou’redone,theapplicationlookslikeFigure10-1.

Figure10-1.Theuserinterfaceforthedialogfragmentsampleapplication

DialogSample:MainActivityLet’sgettothesourcecode,whichyoucandownloadfromthebook’swebsite(seethe“References”section).We’llusetheDialogFragmentDemoproject.OpenupthesourcecodeforMainActivity.javabeforewecontinue.

Thecodeforthemainactivityisverystraightforward.Youdisplayasimplepageoftextandsetupamenu.Eachmenuiteminvokesanactivitymethod,andeachmethoddoesbasicallythesamething:getsafragmenttransaction,createsanewfragment,andshows

thefragment.Notethateachfragmenthasauniquetagthat’susedwiththefragmenttransaction.Thistagbecomesassociatedwiththefragmentinthefragmentmanager,soyoucanlocatethesefragmentslaterbytagname.ThefragmentcanalsodetermineitsowntagvaluewiththegetTag()methodonFragment.

ThelastmethoddefinitioninthemainactivityisonDialogDone(),whichisacallbackthatispartoftheOnDialogDoneListenerinterfacethatyouractivityisimplementing.Asyoucansee,thecallbacksuppliesatagofthefragmentthatiscallingyou,abooleanvalueindicatingwhetherthedialogfragmentwascancelled,andamessage.Foryourpurposes,youmerelywanttologtheinformationtoLogCat;youalsoshowittotheuserusingToast.Toastwillbecoveredlaterinthischapter.

DialogSample:OnDialogDoneListenerSothatyoucanknowwhenadialoghasgoneaway,createalistenerinterfacethatyourdialogcallersimplement.ThecodeoftheinterfaceisinOnDialogDoneListener.java.

Thisisaverysimpleinterface,asyoucansee.Youchooseonlyonecallbackforthisinterface,whichtheactivitymustimplement.Yourfragmentsdon’tneedtoknowthespecificsofthecallingactivity,onlythatthecallingactivitymustimplementtheOnDialogDoneListenerinterface;thereforethefragmentscancallthiscallbacktocommunicatewiththecallingactivity.Dependingonwhatthefragmentisdoing,therecouldbemultiplecallbacksintheinterface.Forthissampleapplication,you’reshowingtheinterfaceseparatelyfromthefragmentclassdefinitions.Foreasiermanagementofcode,youcouldembedthefragmentlistenerinterfaceinsideofthefragmentclassdefinitionitself,thusmakingiteasiertokeepthelistenerandthefragmentinsyncwitheachother.

DialogSample:PromptDialogFragmentNowlet’slookatyourfirstfragment,PromptDialogFragment,whoselayoutisin/res/layout/prompt_dialog.xmlandJavacodeisunder/srcinPromptDialogFragment.java.

Thispromptdialoglayoutlookslikemanyyou’veseenpreviously.ThereisaTextViewtoserveastheprompt;anEditTexttotaketheuser’sinput;andthreebuttonsforsavingtheinput,dismissing(cancelling)thedialogfragment,andpoppingahelpdialog.

ThePromptDialogFragmentJavacodestartsoutlookingjustlikeyourearlierfragments.YouhaveanewInstance()staticmethodtocreatenewobjects,andwithinthismethodyoucallthedefaultconstructor,buildanargumentsbundle,andattachittoyournewobject.Next,youhavesomethingnewintheonAttach()callback.YouwanttomakesuretheactivityyoujustgotattachedtohasimplementedtheOnDialogDoneListenerinterface.Inordertotestthat,youcasttheactivitypassedintotheOnDialogDoneListenerinterface.Here’sthatcode:

try{OnDialogDoneListenertest=(OnDialogDoneListener)act;}

catch(ClassCastExceptioncce){//Hereiswherewefailgracefully.Log.e(MainActivity.LOGTAG,"Activityisnotlistening");}

Iftheactivitydoesnotimplementthisinterface,aClassCastExceptionisthrown.Youcouldhandlethisexceptionanddealwithitmoregracefully,butthisexamplekeepsthecodeassimpleaspossible.

NextupistheonCreate()callback.Asiscommonwithfragments,youdon’tbuildyouruserinterfacehere,butyoucansetthedialogstyle.Thisisuniquetodialogfragments.Youcansetboththestyleandthethemeyourself,oryoucansetjuststyleanduseathemevalueofzero(0)toletthesystemchooseanappropriatethemeforyou.Here’sthatcode:

intstyle=DialogFragment.STYLE_NORMAL,theme=0;setStyle(style,theme);

InonCreateView()youcreatetheviewhierarchyforyourdialogfragment.Justlikeotherfragments,youdonotattachyourviewhierarchytotheviewcontainerpassedin(thatis,bysettingtheattachToRootparametertofalse).Youthenproceedtosetupthebuttoncallbacks,andyousetthedialogprompttexttothepromptthatwaspassedoriginallytonewInstance().

TheonCancel()andonDismiss()callbacksarenotshownbecausealltheydoislogging;you’llbeabletoseewhenthesecallbacksfireduringthefragment’slifecycle.

Thefinalcallbackinthepromptdialogfragmentisforthebuttons.Onceagain,yougrabareferencetoyourenclosingactivityandcastittotheinterfaceyouexpecttheactivitytohaveimplemented.IftheuserpressedtheSavebutton,yougrabthetextasenteredandcalltheinterface’scallbackonDialogDone().Thiscallbacktakesthetagnameofthisfragment,abooleanindicatingwhetherthisdialogfragmentwascancelled,andamessage,whichinthiscaseisthetexttypedbytheuser.HereitisfromtheMainActivity:

publicvoidonDialogDone(Stringtag,booleancancelled,CharSequencemessage){Strings=tag+"respondswith:"+message;if(cancelled)s=tag+"wascancelledbytheuser";Toast.makeText(this,s,Toast.LENGTH_LONG).show();Log.v(LOGTAG,s);}

TofinishhandlingaclickontheSavebutton,youthencalldismiss()togetridofthedialogfragment.Rememberthatdismiss()notonlymakesthefragmentgoawayvisually,butalsopopsthefragmentoutofthefragmentmanagersoitisnolongeravailabletoyou.

IfthebuttonpressedisDismiss,youagaincalltheinterfacecallback,thistimewithno

message,andthenyoucalldismiss().Andfinally,iftheuserpressedtheHelpbutton,youdon’twanttolosethepromptdialogfragment,soyoudosomethingalittledifferent.Wedescribedthisearlier.Inordertorememberthepromptdialogfragmentsoyoucancomebacktoitlater,youneedtocreateafragmenttransactiontoremovethepromptdialogfragmentandaddthehelpdialogfragmentwiththeshow()method;thisneedstogoontothebackstack.Notice,too,howthehelpdialogfragmentiscreatedwithareferencetoaresourceID.Thismeansyourhelpdialogfragmentcanbeusedwithanyhelptextavailabletoyourapplication.

DialogSample:HelpDialogFragmentYoucreatedafragmenttransactiontogofromthepromptdialogfragmenttothehelpdialogfragment,andyouplacedthatfragmenttransactiononthebackstack.Thishastheeffectofmakingthepromptdialogfragmentdisappearfromview,butit’sstillaccessiblethroughthefragmentmanagerandthebackstack.Thenewhelpdialogfragmentappearsinitsplaceandallowstheusertoreadthehelptext.Whentheuserdismissesthehelpdialogfragment,thefragmentbackstackentryispopped,withtheeffectofthehelpdialogfragmentbeingdismissed(bothvisuallyandfromthefragmentmanager)andthepromptdialogfragmentrestoredtoview.Thisisaprettyeasywaytomakeallthishappen.Itisverysimpleyetverypowerful;itevenworksiftheuserrotatesthedevicewhilethesedialogsarebeingdisplayed.

LookatthesourcecodeoftheHelpDialogFragment.javafileanditslayout(help_dialog.xml).Thepointofthisdialogfragmentistodisplayhelptext.ThelayoutisaTextViewandaClosebutton.TheJavacodeshouldbestartingtolookfamiliartoyou.There’sanewInstance()methodtocreateanewhelpdialogfragment,anonCreate()methodtosetthestyleandtheme,andanonCreateView()methodtobuildtheviewhierarchy.Inthisparticularcase,youwanttolocateastringresourcetopopulatetheTextView,soyouaccesstheresourcesthroughtheactivityandchoosetheresourceIDthatwaspassedintonewInstance().Finally,onCreateView()setsupabutton-clickhandlertocapturetheclicksoftheClosebutton.Inthiscase,youdon’tneedtodoanythinginterestingatthetimeofdismissal.

Thisfragmentiscalledtwoways:fromtheactivityandfromthepromptdialogfragment.Whenthishelpdialogfragmentisshownfromthemainactivity,dismissingitsimplypopsthefragmentoffthetopandrevealsthemainactivityunderneath.Whenthishelpdialogfragmentisshownfromthepromptdialogfragment,becausethehelpdialogfragmentwaspartofafragmenttransactiononthebackstack,dismissingitcausesthefragmenttransactiontoberolledback,whichpopsthehelpdialogfragmentbutrestoresthepromptdialogfragment.Theuserseesthepromptdialogfragmentreappear.

DialogSample:AlertDialogFragmentWehaveonelastdialogfragmenttoshowyouinthissampleapplication:thealertdialogfragment.Althoughyoucouldcreateanalertdialogfragmentinawaysimilartothehelpdialogfragment,youcanalsocreateadialogfragmentusingtheoldAlertBuilderframeworkthathasworkedformanyreleasesofAndroid.Lookatthesourcecodein

AlertDialogFragment.java.

Youdon’tneedalayoutforthisonebecausetheAlertBuildertakescareofthatforyou.Notethatthisdialogfragmentstartsoutlikeanyother,butinsteadofanonCreateView()callback,youhaveaonCreateDialog()callback.YouimplementeitheronCreateView()oronCreateDialog()butnotboth.ThereturnfromonCreateDialog()isnotaview;it’sadialog.Ofinteresthereisthattogetparametersforthedialog,youshouldbeaccessingyourargumentsbundle.Inthisexampleapplication,youonlydothisforthealertmessage,butyoucouldaccessotherparametersthroughtheargumentsbundleaswell.

Noticealsothatwiththistypeofdialogfragment,youneedyourfragmentclasstoimplementtheDialogInterface.OnClickListener,whichmeansyourdialogfragmentmustimplementtheonClick()callback.Thiscallbackisfiredwhentheuseractsontheembeddeddialog.Onceagain,yougetareferencetothedialogthatfiredandanindicationofwhichbuttonwaspressed.Asbefore,youshouldbecarefulnottodependonanonDismiss()becausethiscouldfirewhenthereisadeviceconfigurationchange.

DialogSample:EmbeddedDialogsThere’sonemorefeatureofaDialogFragmentthatyoumayhavenoticed.Inthemainlayoutfortheapplication,underthetext,isaFrameLayoutthatcanbeusedtoholdadialog.Intheapplication’smenu,thelastitemcausesafragmenttransactiontoaddanewinstanceofaPromptDialogFragmenttothemainscreen.Withoutanymodifications,thedialogfragmentcanbedisplayedembeddedinthemainlayout,anditfunctionsasyouwouldexpect.

Onethingthatisdifferentaboutthistechniqueisthatthecodetoshowtheembeddeddialogisnotthesameasthecodetodoapop-updialog.Theembeddeddialogcodelookslikethis:

ft.add(R.id.embeddedDialog,pdf,EMBED_DIALOG_TAG);ft.commit();

ThislooksjustthesameasinChapter8,whenwedisplayedafragmentinaFrameLayout.Thistime,however,youmakesuretopassinatagname,whichisusedwhenthedialogfragmentnotifiesyouractivityoftheuser’sinput.

DialogSample:ObservationsWhenyourunthissampleapplication,makesureyoutryallthemenuoptionsindifferentorientationsofthedevice.Rotatethedevicewhilethedialogfragmentsaredisplayed.Youshouldbepleasedtoseethatthedialogsgowiththerotations;youdonotneedtoworryaboutalotofcodetomanagethesavingandrestoringoffragmentsduetoconfigurationchanges.

Theotherthingwehopeyouappreciateistheeasewithwhichyoucancommunicatebetweenthefragmentsandtheactivity.Ofcourse,theactivityhasreferences,orcanget

references,toalltheavailablefragments,soitcanaccessmethodsexposedbythefragmentsthemselves.Thisisn’ttheonlywaytocommunicatebetweenfragmentsandtheactivity.Youcanalwaysusethegettermethodsonthefragmentmanagertoretrieveaninstanceofamanagedfragment,andthencastthatreferenceappropriatelyandcallamethodonthatfragmentdirectly.Youcanevendothisfromwithinanotherfragment.Thedegreetowhichyouisolateyourfragmentsfromeachotherwithinterfacesandthroughactivities,orbuildindependencieswithfragment-to-fragmentcommunication,isbasedonhowcomplexyourapplicationisandhowmuchreuseyouwanttoachieve.

WorkingwithToastAToastislikeaminialertdialogthathasamessageanddisplaysforacertainamountoftimeandthengoesawayautomatically.Itdoesnothaveanybuttons.Soitcanbesaidthatitisatransientalertmessage.It’scalledToastbecauseitpopsupliketoastoutofatoaster.

Listing10-10showsanexampleofhowyoucanshowamessageusingToast.

Listing10-10.UsingToastforDebugging

//Createafunctiontowrapamessageasatoast//showthetoastpublicvoidreportToast(Stringmessage){Strings=MainActivity.LOGTAG+":"+message;Toast.makeText(activity,s,Toast.LENGTH_SHORT).show();}

ThemakeText()methodinListing10-10cantakenotonlyanactivitybutanycontextobject,suchastheonepassedtoabroadcastreceiveroraservice,forexample.ThisextendstheuseofToastoutsideofactivities.

Referenceswww.androidbook.com/proandroid5/projects:Thischapter’stestproject.ThenameoftheZIPfileisProAndroid5_ch10_Dialogs.zip.Thedownloadincludesanexampleofthedate-andtime-pickerdialogsinPickerDialogFragmentDemo.

http://developer.android.com/guide/topics/ui/dialogs.htmlAndroidSDKdocumentthatprovidesanexcellentintroductiontoworkingwithAndroiddialogs.Youwillfindhereanexplanationofhowtousemanageddialogsandvariousexamplesofavailabledialogs.

http://developer.android.com/reference/android/content/DialogInterface.htmlThemanyconstantsdefinedfordialogs.

http://developer.android.com/reference/android/app/AlertDialog.Builder.htmlAPIdocumentationfortheAlertDialogbuilderclass.

http://developer.android.com/reference/android/app/ProgressDialog.htmlAPIdocumentationforProgressDialog.

http://developer.android.com/guide/topics/ui/controls/pickers.htmlAnAndroidtutorialforusingthedate-pickerandtime-pickerdialogs.

SummaryThischapterdiscussedasynchronousdialogsandhowtousedialogfragments,includingthefollowingtopics:

Whatadialogisandwhyyouuseone

TheasynchronousnatureofadialoginAndroid

Thethreestepsofgettingadialogtodisplayonthescreen

Creatingafragment

Twomethodsforhowadialogfragmentcancreateaviewhierarchy

Howafragmenttransactionisinvolvedindisplayingadialogfragment,andhowtogetone

WhathappenswhentheuserpressestheBackbuttonwhileviewingadialogfragment

Thebackstack,andmanagingdialogfragments

Whathappenswhenabuttononadialogfragmentisclicked,andhowyoudealwithit

Acleanwaytocommunicatebacktothecallingactivityfromadialogfragment

Howonedialogfragmentcancallanotherdialogfragmentandstillgetbacktothepreviousdialogfragment

TheToastclassandhowitcanbeusedasasimplealertpop-up

Chapter11

WorkingwithPreferencesandSavingStateAndroidoffersarobustandflexibleframeworkfordealingwithsettings,alsoknownaspreferences.Andbysettings,wemeanthosefeaturechoicesthatausermakesandsavestocustomizeanapplicationtotheirliking.(Inthischapter,thetermssettingsandpreferenceswillbeusedinterchangeably.)Forexample,iftheuserwantsanotificationviaaringtoneorvibrationornotatall,thatisapreferencetheusersaves;theapplicationremembersthechoiceuntiltheuserchangesit.AndroidprovidessimpleAPIsthathidethemanagementandpersistingofpreferences.Italsoprovidesprebuiltuserinterfacesthatyoucanusetolettheusermakepreferenceselections.BecauseofthepowerbuiltintotheAndroidpreferencesframework,wecanalsousepreferencesformoregeneral-purposestoringofapplicationstate,toallowourapplicationtopickupwhereitleftoff,shouldourapplicationgoawayandcomebacklater.Asanotherexample,agame’shighscorescouldbestoredaspreferences,althoughyou’llwanttouseyourownUItodisplaythem.

Thischaptercovershowtoimplementyourownsettingsscreensforyourapplication,howtointeractwithAndroidsystemsettings,andhowtousesettingstosecretlysaveapplicationstate,anditalsoprovidesbest-practiceguidance.You’lldiscoverhowtomakeyoursettingslookgoodonsmallscreensaswellaslargerscreenssuchasthosefoundontablets.

ExploringthePreferencesFrameworkAndroid’spreferencesframeworkbuildsfromtheindividualsettingschoices,toahierarchyofscreensthatcontainsettingschoices.Settingscouldbebinarysettingssuchason/off,ortextinput,oranumericvalue,orcouldbeaselectionfromalistofchoices.AndroidusesaPreferenceManagertoprovidesettingsvaluestoapplications.Theframeworktakescareofmakingandpersistingchanges,andnotifyingtheapplicationwhenasettingchangesorisabouttochange.Whilesettingsarepersistedinfiles,applicationsdon’tdealdirectlywiththefiles.Thefilesarehiddenaway,andyou’llseeshortlywheretheyare.

AswithviewscoveredinChapter3,preferencescanbespecifiedwithXML,orbywritingcode.Forthischapter,you’llworkwithasampleapplicationthatdemonstratesthedifferenttypesofchoices.XMListhepreferredwaytospecifyapreference,sothatishowtheapplicationwaswritten.XMLspecifiesthelowest-levelsettings,plushowtogroupsettingstogetherintocategoriesandscreens.Forreference,thesampleapplicationforthischapterpresentsthefollowingsettingsasshowninFigure11-1.

Figure11-1.ThemainsettingsfromthesampleapppreferenceUI.Duetothescreen’sheight,ithasbeenshownwiththetopontheleftandthebottomontheright.Noticetheoverlapbetweenthetwoimages

Androidprovidesanend-to-endpreferencesframework.Thismeanstheframeworkletsyoudefineyourpreferences,displaythesetting(s)totheuser,andpersisttheuser’sselectiontothedatastore.YoudefineyourpreferencesinXMLunder/res/xml/.Toshowpreferencestotheuser,youwriteanactivityclassthatextendsapredefinedAndroidclasscalledandroid.preference.PreferenceActivityandusefragmentstohandlethescreensofpreferences.Theframeworktakescareoftherest(displayingandpersisting).Withinyourapplication,yourcodewillgetreferencestospecificpreferences.Withapreferencereference,youcangetthecurrentvalueofthepreference.

Inorderforpreferencestobesavedacrossusersessions,thecurrentvaluesmustbesavedsomewhere.TheAndroidframeworktakescareofpersistingpreferencesinanXMLfilewithintheapplication’s/data/datadirectoryonthedevice(seeFigure11-2).

Figure11-2.Pathtoanapplication’ssavedpreferences

NoteYouwillbeabletoinspectsharedpreferencesfilesintheemulatoronly.Onarealdevice,thesharedpreferencesfilesarenotreadableduetoAndroidsecurity(unlessyouhaverootprivileges,ofcourse).

Thedefaultpreferencesfilepathforanapplicationis/data/data/[PACKAGE_NAME]/shared_prefs/[PACKAGE_NAME]_preferences.xml,where[PACKAGE_NAME]isthepackageoftheapplication.Listing11-1showsthecom.androidbook.preferences.main_preferences.xmldatafileforthisexample.

Listing11-1.SavedPreferencesforOurExample

<?xmlversion='1.0'encoding='utf-8'standalone='yes'?><map><booleanname="notification_switch"value="true"/><stringname="package_name_preference">com.androidbook.win</string><booleanname="potato_selection_pref"value="true"/><booleanname="show_airline_column_pref"value="true"/><stringname="flight_sort_option">2</string><booleanname="alert_email"value="false"/><setname="pizza_toppings"><string>pepperoni</string><string>cheese</string><string>olive</string></set><stringname="alert_email_address">davemac327@gmail.com</string></map>

Asyoucansee,valuesarestoredinamap,withpreferencekeysasnamestothedatavalues.Someofthevalueslookcrypticanddonotmatchwhatisdisplayedtotheuser.Forexample,thevalueforflight_sort_optionis2.Androiddoesnotstorethedisplayedtextas

thevalueofthepreference;rather,itstoresavaluethattheuserwon’tsee,thatyoucanuseindependentlyofwhattheusersees.Youwantthefreedomtochangethedisplayedtextbasedontheuser’slanguage,andyoualsowanttheabilitytotweakthedisplayedtextwhilekeepingthesamestoredvalueinthepreferencesfile.Youmightevenbeabletodosimplerprocessingofthepreferenceifthevalueisanintegerinsteadofsomedisplaystring.Whatyoudon’thavetoworryaboutisparsingthisdatafile.TheAndroidpreferencesframeworkprovidesaniceAPIfordealingwithpreferences,whichwillbedescribedinmoredetaillaterinthischapter.IfyoucomparethepreferencesmapinListing11-1withthescreenshotsinFigure11-1,youwillnoticethatnotallpreferencesarelistedwithvaluesinthepreferencesXMLdatafile.Thisisbecausethepreferencedatafiledoesnotautomaticallystoreadefaultvalueforyou.You’llseeshortlyhowtodealwithdefaultvalues.

Nowthatyou’veseenwherethevaluesaresaved,youneedtoseehowtodefinethescreenstodisplaytotheusersotheycanmakeselections.Beforeyouseehowtocollectpreferencestogetherintoscreens,you’lllearnaboutthedifferenttypesofpreferencesyoucanuse,andthenyou’llseehowtoputthemtogetherintoscreens.Eachpersistedvalueinthe/data/dataXMLfileisfromaspecificpreference.Solet’sunderstandwhateachofthesemeans.

UnderstandingCheckBoxPreferenceandSwitchPreferenceThesimplestofthepreferencesaretheCheckBoxPreferenceandSwitchPreference.Theseshareacommonparentclass(TwoStatePreference)andareeitheron(valueistrue)oroff(valueisfalse).Forthesampleapplication,ascreenwascreatedwithfiveCheckBoxPreferences,asshowninFigure11-3.Listing11-2showswhattheXMLlookslikeforaCheckBoxPreference.

Figure11-3.Theuserinterfaceforthecheckboxpreference

Listing11-2.UsingCheckBoxPreference

<CheckBoxPreferenceandroid:key="show_airline_column_pref"android:title="Airline"android:summary="ShowAirlinecolumn"/>

NoteWewillgiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.ThemainsampleapplicationiscalledPrefDemo.YoushouldrefertothatprojectuntilyoucometotheSavingStatesection.

Thisexampleshowstheminimumthat’srequiredtospecifyapreference.Thekeyisthereferenceto,ornameof,thepreference,thetitleisthetitledisplayedforthepreference,andsummaryisadescriptionofwhatthepreferenceisfororastatusofthecurrentsetting.LookingbackonthesavedvaluesinListing11-1,youwillseea<boolean>tagfor“show_airline_column_pref”(thekey),andithasanattributevalueoftrue,whichindicatesthatthepreferenceischeckedon.

WithCheckBoxPreference,thestateofthepreferenceissavedwhentheusersetsthestate.Inotherwords,whentheuserchecksorunchecksthepreferencecontrol,itsstateissavedimmediately.

TheSwitchPreferenceisverysimilarexceptthatthevisualdisplayisdifferent.Insteadofacheckboxintheuserinterface,theuserseesanon-offswitch,asshowninFigure11-1nextto“Notificationsare”.

OneotherusefulfeatureofCheckBoxPreferenceandSwitchPreferenceisthatyoucansetdifferentsummarytextdependingonwhetherit’schecked.TheXMLattributesaresummaryOnandsummaryOff.Ifyoulookinthemain.xmlfilefortheCheckBoxPreferencecalled“potato_selection_pref”youwillseeanexampleofthis.

Beforeyoulearntheotherpreferencetypes,nowwouldbeagoodtimetounderstandhowtoaccessthispreferencetoreaditsvalueandperformotheroperations.

AccessingaPreferenceValueinCodeNowthatyouhaveapreferencedefinedyouneedtoknowhowtoaccessthepreferenceincodesoyoucanreadthevalue.Listing11-3showscodetoaccesstheSharedPreferencesobjectinAndroidwherethepreferencesexist.ThiscodeisfromtheMainActivity.javafileinthesetOptionText()method.

Listing11-3.AccessingtheCheckBoxPreferenceSharedPreferencesprefs=

PreferenceManager.getDefaultSharedPreferences(this);

//Thisistheotherwaytogettothesharedpreferences://SharedPreferencesprefs=getSharedPreferences(//“com.androidbook.preferences.main_preferences”,0);booleanshowAirline=prefs.getBoolean(“show_airline_column_pref”,false);

Usingthereferencetopreferences,itisstraightforwardtoreadthecurrentvalueoftheshow_airline_column_prefpreference.AsshowninListing11-3,therearetwowaystogettothepreferences.Thefirstwayshownistogetthedefaultpreferencesforthecurrentcontext.Inthiscase,thecontextisthatoftheMainActivityofourapplication.Thesecondcase,whichisshowncommentedout,retrievesthepreferencesusingapackagename.Youcouldusewhateverpackagenameyouwantincaseyouneedtostoredifferentsetsofpreferencesindifferentfiles.

Onceyouhaveareferencetothepreferences,youcalltheappropriategettermethodwiththekeyofthepreferenceandadefaultvalue.Sinceshow_airline_column_prefisaTwoStatePreference,thevaluereturnedisaboolean.Thedefaultvalueforshow_airline_column_prefishard-codedhereasfalse.Ifthispreferencehasnotyetbeensetatall,thehard-codedvalue(false)willbeassignedtoshowAirline.However,thatbyitselfdoesnotpersistthepreferencetofalseforfutureuse,nordoesithonoranydefaultvaluethatmighthavebeensetintheXMLspecificationforthispreference.IftheXMLspecificationusesaresourcevaluetospecifythedefaultvalue,thenthesameresourcecouldbereferredtoincodetosetthedefaultvalue,asshowninthefollowingforadifferentpreference:

Stringflight_option=prefs.getString(resources.getString(R.string.flight_sort_option),resources.getString(R.string.flight_sort_option_default_value));

Noticeherethatthekeyforthepreferenceisalsousingastringresourcevalue(R.string.flight_sort_option).Thiscanbeawisechoicesinceitmakestyposlesslikely.Iftheresourcenameistypedwrongyou’llverylikelygetabuilderror.Ifyouusejustsimplestrings,itispossibleforatypotogounnoticed,exceptthatyourpreferenceswon’twork.

Weshowedonewaytoreadadefaultvalueforapreferenceincode.Androidprovidesanotherwaythatisabitmoreelegant.InonCreate(),youcandothefollowinginstead:

PreferenceManager.setDefaultValues(this,R.xml.main,false);

Then,insetOptionText(),youwouldhavedonethistoreadtheoptionvalue:

Stringoption=prefs.getString(resources.getString(R.string.flight_sort_option),null);

Thefirstcallwillusemain.xmltofindthedefaultvaluesandgeneratethepreferencesXMLdatafileforususingthedefaultvalues.Ifwealreadyhaveaninstanceofthe

SharedPreferencesobjectinmemory,itwillupdatethattoo.Thesecondcallwillthenfindavalueforflight_sort_option,becausewetookcareofloadingdefaultsfirst.

Afterrunningthiscodethefirsttime,ifyoulookintheshared_prefsfolder,youwillseethepreferencesXMLfileevenifthepreferencesscreenhasnotyetbeeninvoked.Youwillalsoseeanotherfilecalled_has_set_default_values.xml.ThisfiletellsyourapplicationthatthepreferencesXMLfilehasalreadybeencreatedwiththedefaultvalues.ThethirdargumenttosetDefaultValues()—thatis,false—indicatesthatyouwantthedefaultssetinthepreferencesXMLfileonlyifithasn’tbeendonebefore.AndroidremembersthisinformationthroughtheexistenceofthisnewXMLfile.However,Androidremembersevenifyouupgradeyourapplicationandaddnewsettingswithnewdefaultvalues,whichmeansthistrickwon’tsetthosenewdefaults.Yourbestoptionistoalwaysusearesourceforthedefaultvalue,andalwaysprovidethatresourceasthedefaultvaluewhengettingthecurrentvalueofapreference.

UnderstandingListPreferenceAlistpreferencecontainsradiobuttonsforeachoption,andthedefault(orcurrent)selectionispreselected.Theuserisexpectedtoselectoneandonlyoneofthechoices.Whentheuserchoosesanoption,thedialogisimmediatelydismissedandthechoiceissavedinthepreferencesXMLfile.Figure11-4showswhatthislookslike.

Figure11-4.TheuserinterfacefortheListPreference

Listing11-4containsanXMLfragmentthatrepresentstheflight-optionpreferencesetting.Thistimethefilecontainsreferencestostringsandtoarrays,whichwouldbethe

morecommonwaytospecifytheseratherthanhard-codingthestrings.Asmentionedbefore,thevalueofalistpreferenceasstoredintheXMLdatafileunderthe/data/data/{package}directoryisnotthesameaswhattheuserseesintheuserinterface.Thenameofthekeyisstoredinthedatafile,alongwithahiddenvaluethattheuserdoesnotsee.Therefore,togetaListPreferencetowork,thereneedstobetwoarrays:thevaluesdisplayedtotheuserandthestringsusedaskeyvalues.Thisiswhereyoucaneasilygettrippedup.Theentriesarrayholdsthestringsdisplayedtotheuser,andtheentryValuesarrayholdsthestringsthatwillbestoredinthepreferencesdataXMLfile.

Listing11-4.SpecifyingaListPreferenceinXML

<ListPreferenceandroid:key="@string/flight_sort_option"android:title="@string/listTitle"android:summary="@string/listSummary"android:entries="@array/flight_sort_options"android:entryValues="@array/flight_sort_options_values"android:dialogTitle="@string/dialogTitle"android:defaultValue="@string/flight_sort_option_default_value"/>

Theelementsbetweenthetwoarrayscorrespondtoeachotherpositionally.Thatis,thethirdelementintheentryValuesarraycorrespondstothethirdelementintheentriesarray.Itistemptingtouse0,1,2,etc.,asentryValuesbutitisnotrequired,anditcouldcauseproblemslaterwhenthearraysmustbemodified.Ifouroptionwerenumericinnature(forexample,acountdowntimerstartingvalue),thenwecouldhaveusedvaluessuchas60,120,300,andsoon.Thevaluesdon’tneedtobenumericatallaslongastheymakesensetothedeveloper;theuserdoesn’tseethesevaluesunlessyouchoosetoexposethem.Theuseronlyseesthetextfromthefirststringarrayflight_sort_options.Theexampleapplicationforthischaptershowsitbothways.

Awordofcautionhere:becausethepreferencesXMLdatafileisstoringonlythevalueandnotthetext,shouldyoueverupgradeyourapplicationandchangethetextoftheoptionsoradditemstothestringarrays,anyvaluestoredinthepreferencesXMLdatafileshouldstilllineupwiththeappropriatetextaftertheupgrade.ThepreferencesXMLdatafileiskeptduringtheapplicationupgrade.IfthepreferencesXMLdatafilehada“1”init,andthatmeant“#ofStops”beforetheupgrade,itshouldstillmean“#ofStops”aftertheupgrade.

SincetheentryValuesarrayisnotseenbytheenduser,itisbestpracticetostoreitonceandonlyoncewithinyourapplication.Therefore,makeoneandonlyone/res/values/prefvaluearrays.xmlfiletocontainthesearrays.Theentriesarrayisverylikelytobecreatedmultipletimesperapplication,fordifferentlanguagesorperhapsdifferentdeviceconfigurations.Therefore,makeseparateprefdisplayarrays.xmlfilesforeachvariationthatyouneed.Forexample,ifyourapplicationwillbeusedinEnglishandinFrench,therewillbeseparate

prefdisplayarrays.xmlfilesforEnglishandFrench.YoudonotwanttoincludetheentryValuesarrayineachoftheseotherfiles.ItisimperativethoughthattherearethesamenumbersofarrayelementsbetweenentryValuesandentriesarrays.Theelementsmustlineup.Whenyoumakechanges,becarefultokeepeverythinginalignment.Listing11-5containsthesourceofListPreferencefilesfortheexample.

Listing11-5.OtherListPreferenceFilesfromOurExample

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/prefvaluearrays.xml--><resources><string-arrayname="flight_sort_options_values"><item>0</item><item>1</item><item>2</item></string-array><string-arrayname="pizza_toppings_values"><item>cheese</item><item>pepperoni</item><item>onion</item><item>mushroom</item><item>olive</item><item>ham</item><item>pineapple</item></string-array><string-arrayname="default_pizza_toppings"><item>cheese</item><item>pepperoni</item></string-array></resources>

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/prefdisplayarrays.xml--><resources><string-arrayname="flight_sort_options"><item>TotalCost</item><item>#ofStops</item><item>Airline</item></string-array><string-arrayname="pizza_toppings"><item>Cheese</item><item>Pepperoni</item><item>Onions</item><item>PortobelloMushrooms</item><item>BlackOlives</item><item>SmokedHam</item><item>Pineapple</item>

</string-array></resources>

Also,don’tforgetthatyourdefaultvalueasspecifiedintheXMLsourcefilemustmatchanentryValueinthearrayfromprefvaluearrays.xml.

ForaListPreference,thevalueofthepreferenceisaString.Ifyouareusingnumberstrings(e.g.,0,1,1138)asentryValues,youcouldconvertthosetointegersorwhateveryouneedinyourcode,asisusedintheflight_sort_options_valuesarray.

Yourcodeislikelygoingtowanttodisplaytheuser-friendlytextfromthepreference’sentriesarray.Thisexampletookashortcut,becausearrayindiceswereusedfortheelementsinflight_sort_options_values.Bysimplyconvertingthevaluetoanint,youknowwhichstringtoreadfromflight_sort_options.Hadyouusedsomeothersetofvaluesforflight_sort_options_values,youwouldneedtodeterminetheindexoftheelementthatisyourpreferenceandthenturnaroundandusethatindextograbthetextofyourpreferencefromflight_sort_options.ListPreference’shelpermethodfindIndexOfValue()canhelpwiththis,byprovidingtheindexintothevaluesarraysoyoucantheneasilygetthecorrespondingdisplaytextfromtheentriesarray.

ReturningnowtoListing11-4,thereareseveralstringsfortitles,summaries,andmore.Thestringcalledflight_sort_option_default_valuesetsthedefaultvalueto1torepresent“#ofStops”intheexample.Itisusuallyagoodideatochooseadefaultvalueforeachoption.Ifyoudon’tchooseadefaultvalueandnovaluehasyetbeenchosen,themethodsthatreturnthevalueoftheoptionwillreturnnull.Yourcodewouldhavetodealwithnullvaluesinthiscase.

UnderstandingEditTextPreferenceThepreferencesframeworkalsoprovidesafree-formtextpreferencecalledEditTextPreference.Thispreferenceallowsyoutocapturerawtextratherthanasktheusertomakeaselection.Todemonstratethis,let’sassumeyouhaveanapplicationthatgeneratesJavacodefortheuser.Oneofthepreferencesettingsofthisapplicationmightbethedefaultpackagenametouseforthegeneratedclasses.Here,youwanttodisplayatextfieldtotheuserforsettingthepackagenameforthegeneratedclasses.Figure11-5showstheUI,andListing11-6showstheXML.

Figure11-5.UsingtheEditTextPreference

Listing11-6.AnExampleofanEditTextPreference

<EditTextPreferenceandroid:key="package_name_preference"android:title="SetPackageName"android:summary="Setthepackagenameforgeneratedcode"android:dialogTitle="PackageName"/>

WhenSetPackageNameisselected,theuserispresentedwithadialogtoinputthepackagename.WhentheOKbuttonisclicked,thepreferenceissavedtothepreferencestore.

Aswiththeotherpreferences,youcanobtainthevalueofthepreferencebycallingtheappropriategettermethod,inthiscasegetString().

UnderstandingMultiSelectListPreferenceAndfinally,apreferencecalledMultiSelectListPreferencewasintroducedinAndroid3.0.TheconceptissomewhatsimilartoaListPreference,butinsteadofonlybeingabletoselectoneiteminthelist,theusercanselectseveralornone.InListing11-1,theMultiSelectListPreferencestoresa<setname=“pizza_toppings”>taginthepreferencesXMLdatafile,insteadofasinglevalue.TheothersignificantdifferencewithaMultiSelectListPreferenceisthatthedefaultvalueisanarrayjustliketheentryValuesarray.Thatis,thearrayforthedefaultvaluesmustcontainzeroormoreoftheelementsfromtheentryValuesarrayforthispreference.Thiscanalsobeseeninthesampleapplicationforthischapter;justviewtheendofthemain.xmlfileinthe/res/xmldirectory.

TogetthecurrentvalueofaMultiSelectListPreference,usethegetStringSet()methodofSharedPreferences.Toretrievethedisplaystringsfromtheentriesarray,youwouldneedtoiteratethroughthesetofstringsthatisthevalue

ofthispreference,determinetheindexofthestring,andusetheindextoaccesstheproperdisplaystringfromtheentriesarray.

UpdatingAndroidManifest.xmlBecausetherearetwoactivitiesinthesampleapplication,weneedtwoactivitytagsinAndroidManifest.xml.ThefirstoneisastandardactivityofcategoryLAUNCHER.ThesecondoneisforaPreferenceActivity,sosettheactionnameaccordingtoconventionforintents,andsetthecategorytoPREFERENCEasshowninListing11-7.Youprobablydon’twantthePreferenceActivityshowingupontheAndroidpagewithallourotherapplications,whichiswhyyoudon’tuseLAUNCHERforit.YouwouldneedtomakesimilarchangestoAndroidManifest.xmlifyouweretoaddotherpreferenceactivities.

Listing11-7.PreferenceActivityEntryinAndroidManifest.xml<activityandroid:name=”.MainPreferenceActivity”

android:label=”@string/prefTitle”><intent-filter><actionandroid:name=“com.androidbook.preferences.main.intent.action.MainPreferences”/><categoryandroid:name=“android.intent.category.PREFERENCE”/></intent-filter></activity>

UsingPreferenceCategoryThepreferencesframeworkprovidessupportforyoutoorganizeyourpreferencesintocategories.Ifyouhavealotofpreferences,forexample,youcanusePreferenceCategory,whichgroupspreferencesunderaseparatorlabel.Figure11-6showswhatthiscouldlooklike.Noticetheseparatorscalled“MEATS”and“VEGETABLES.”Youcanfindthespecificationsforthesein/res/xml/main.xml.

Figure11-6.UsingPreferenceCategorytoorganizepreferences

CreatingChildPreferenceswithDependencyAnotherwaytoorganizepreferencesistouseapreferencedependency.Thiscreatesaparent-childrelationshipbetweenpreferences.Forexample,youmighthaveapreferencethatturnsonalerts;andifalertsareon,theremightbeseveralotheralert-relatedpreferencestochoosefrom.Ifthemainalertspreferenceisoff,theotherpreferencesarenotrelevantandshouldbedisabled.Listing11-8showstheXML,andFigure11-7showswhatitlookslike.

Listing11-8.PreferenceDependencyinXML

<PreferenceScreen><PreferenceCategoryandroid:title="Alerts">

<CheckBoxPreferenceandroid:key="alert_email"android:title="Sendemail?"/>

<EditTextPreferenceandroid:key="alert_email_address"android:layout="?android:attr/preferenceLayoutChild"android:title="EmailAddress"android:dependency="alert_email"/>

</PreferenceCategory></PreferenceScreen>

Figure11-7.Preferencedependency

PreferenceswithHeadersAndroid3.0introducedanewwaytoorganizepreferences.YouseethisontabletsunderthemainSettingsapp.Becausetabletscreenrealestateoffersmuchmoreroomthanasmartphonedoes,itmakessensetodisplaymorepreferenceinformationatthesametime.Toaccomplishthis,youusepreferenceheaders.TakealookatFigure11-8.

Figure11-8.MainSettingspagewithpreferenceheaders

Noticethatheadersappeardowntheleftside,likeaverticaltabbar.Asyouclickeachitemontheleft,thescreentotherightdisplaysthepreferencesforthatitem.InFigure11-8,Soundischosen,andthesoundpreferencesaredisplayedatright.TherightsideisaPreferenceScreenobject,andthissetupusesfragments.Obviously,weneedtodosomethingdifferentthanwhathasbeendiscussedsofarinthischapter.

ThebigchangefromAndroid3.0wastheadditionofheaderstoPreferenceActivity.ThisalsomeansusinganewcallbackwithinPreferenceActivitytodotheheaderssetup.Now,whenyouextendPreferenceActivity,you’llwanttoimplementthismethod:

publicvoidonBuildHeaders(List<Header>target){loadHeadersFromResource(R.xml.preferences,target);}

PleaserefertothePrefDemosampleapplicationforthecompletesourcecode.Thepreferences.xmlfilecontainssomenewtagsthatlooklikethis:

<preference-headersxmlns:android="http://schemas.android.com/apk/res/android<headerandroid:fragment="com.example.PrefActivity$Prefs1Fragment"android:icon="@drawable/ic_settings_sound"android:title="Sound"android:summary="Yoursoundpreferences"/>...

EachheadertagpointstoaclassthatextendsPreferenceFragment.Intheexamplejustgiven,theXMLspecifiesanicon,thetitle,andsummarytext(whichactslikeasubtitle).Prefs1FragmentisaninnerclassofPreferenceActivitythatcouldlooksomethinglikethis:

publicstaticclassPrefs1FragmentextendsPreferenceFragment{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.sound_preferences);}}

AllthisinnerclassneedstodoispullintheappropriatepreferencesXMLfile,asshown.ThatpreferencesXMLfilecontainsthetypesofpreferencespecificationswecoveredearlier,suchasListPreference,CheckBoxPreference,PreferenceCategory,andsoon.What’sveryniceisthatAndroidtakescareofdoingtherightthingwhenthescreenconfigurationchangesandwhenthepreferencesaredisplayedonasmallscreen.Headersbehavelikeoldpreferenceswhenthescreenistoosmalltodisplaybothheadersandthepreferencescreentotheright.Thatis,youonlysee

theheaders;andwhenyouclickaheader,youthenseeonlytheappropriatepreferencescreen.

PreferenceScreensThetop-levelcontainerforpreferencesisaPreferenceScreen.BeforetabletsandPreferenceFragments,youcouldnestPreferenceScreens,andwhentheuserclickedonanestedPreferenceScreenitem,thenewPreferenceScreenwouldreplacethecurrentlydisplayedPreferenceScreen.Thisworkedfineonasmallscreen,butdoesn’tlookasgoodonatablet,especiallyifyoustartedwithheadersandfragments.WhatyouprobablywantisforthenewPreferenceScreentoappearwherethecurrentfragmentis.

TomakeaPreferenceScreenworkinsideofafragment,allyouneedtodoisspecifyafragmentclassnameforthatPreferenceScreen.Listing11-9showstheXMLfromthesampleapplication.

Listing11-9.PreferenceScreeninvokedviaaPreferenceFragment

<PreferenceScreenandroid:title="Launchanewscreenintoafragment"android:fragment="com.androidbook.preferences.main.BasicFrag"/>

Whentheuserclicksonthisitem,thecurrentfragmentisreplacedwithBasicFrag,whichthenloadsanewXMLlayoutforaPreferenceScreenasspecifiedinnested_screen_basicfrag.xml.Inthiscase,wechosenottomaketheBasicFragclassaninnerclassoftheMainPreferenceActivityclass,mainlybecausethereisnosharingneededfromtheouterclass,andtoshowyouthatyoucandoitthiswayifyouprefer.

DynamicPreferenceSummaryTextYou’veprobablyseenpreferenceswherethepreferencesummarycontainsthecurrentvalue.Thisisactuallyalittlehardertoimplementthanyoumightthink.Toaccomplishthisfeat,youcreatealistenercallbackthatdetectswhenapreferencevalueisabouttochange,andyouthenupdatethepreferencesummaryaccordingly.ThefirststepisforyourPreferenceFragmenttoimplementtheOnPreferenceChangeListenerinterface.YouthenneedtoimplementtheonPreferenceChange()callback.Listing11-10showsanexample.ThepkgPrefobjectinthecallbackwassetearliertothepreferenceintheonCreate()method.

Listing11-10.SettingUpaPreferenceListener

publicbooleanonPreferenceChange(Preferencepreference,ObjectnewValue){

finalStringkey=preference.getKey();if("package_name_preference".equals(key)){pkgPref.setSummary(newValue.toString());}...returntrue;}

YouhavetoregisterthefragmentasalistenerinonResume()usingsetOnPreferenceChangeListener(this)oneachpreferenceyouwanttolistenon,andunregisterinonPause()bycallingitagainwithnull.Noweverytimethereisapendingchangetoapreferenceyou’veregisteredfor,thiscallbackwillbeinvokedpassinginthepreferenceandthepotentialnewvalue.Thecallbackreturnsabooleanindicatingwhethertoproceedwithsettingthepreferencetothenewvalue(true)ornot(false).Assumingyouwouldreturntruetoallowthenewsetting,thisiswhereyoucanupdatethesummaryvalueaswell.Youcouldalsovalidatethenewvalueandrejectthechange.PerhapsyouwantaMultiSelectListPreferencetohaveamaximumnumberofcheckeditems.Youcouldcounttheselecteditemsinthecallbackandrejectthechangeiftherearetoomany.

SavingStatewithPreferencesPreferencesaregreatforallowinguserstocustomizeapplicationstotheirliking,butwecanusetheAndroidpreferenceframeworkformorethanthat.Whenyourapplicationneedstokeeptrackofsomedatabetweeninvocationsoftheapplication,preferencesareonewaytoaccomplishthetaskeveniftheusercan’tseethedatainpreferencescreens.PleasefindthesampleapplicationcalledSavingStateDemotofollowalongwiththecompletesourcecode.

TheActivityclasshasagetPreferences(intmode)method.This,inreality,simplycallsgetSharedPreferences()withtheclassnameoftheactivityasthetagplusthemodeaspassedin.Theresultisanactivity-specificsharedpreferencesfilethatyoucanusetostoredataaboutthisactivityacrossinvocations.AsimpleexampleofhowyoucouldusethisisshowninListing11-11.

Listing11-11.UsingPreferencestoSaveStateforanActivityfinalStringINITIALIZED=“initialized”;

privateStringsomeString;

[…]

SharedPreferencesmyPrefs=getPreferences(MODE_PRIVATE);

booleanhasPreferences=myPrefs.getBoolean(INITIALIZED,false);if(hasPreferences){

Log.v(“Preferences”,“We'vebeencalledbefore”);//Readothervaluesasdesiredfrompreferencesfile…someString=myPrefs.getString(“someString”,””);}else{Log.v(“Preferences”,“Firsttimeeverbeingcalled”);//Setupinitialvaluesforwhatwillendup//inthepreferencesfilesomeString=“somedefaultvalue”;}

[…]

//LaterwhenreadytowriteoutvaluesEditoreditor=myPrefs.edit();editor.putBoolean(INITIALIZED,true);editor.putString(“someString”,someString);//Writeothervaluesasdesirededitor.commit();

Whatthiscodedoesisacquireareferencetopreferencesforouractivityclassandcheckfortheexistenceofaboolean“preference”calledinitialized.Wewrite“preference”indoublequotationmarksbecausethisvalueisnotsomethingtheuserisgoingtoseeorset;it’smerelyavaluethatwewanttostoreinasharedpreferencesfileforusenexttime.Ifwegetavalue,thesharedpreferencesfileexists,sotheapplicationmusthavebeencalledbefore.Youcouldthenreadothervaluesoutofthesharedpreferencesfile.Forexample,someStringcouldbeanactivityvariablethatshouldbesetfromthelasttimethisactivityranorsettothedefaultvalueifthisisthefirsttime.

Towritevaluestothesharedpreferencesfile,youmustfirstgetapreferencesEditor.Youcanthenputvaluesintopreferencesandcommitthosechangeswhenyou’refinished.Notethat,behindthescenes,AndroidismanagingaSharedPreferencesobjectthatistrulyshared.Ideally,thereisnevermorethanoneEditoractiveatatime.Butitisveryimportanttocallthecommit()methodsothattheSharedPreferencesobjectandthesharedpreferencesXMLfilegetupdated.Intheexample,thevalueofsomeStringiswrittenouttobeusedthenexttimethisactivityruns.

Youcanaccess,write,andcommitvaluesanytimetoyourpreferencesfile.Possibleusesforthisincludewritingouthighscoresforagameorrecordingwhentheapplicationwaslastrun.YoucanalsousethegetSharedPreferences()callwithdifferentnamestomanageseparatesetsofpreferences,allwithinthesameapplicationoreventhesameactivity.

MODE_PRIVATEwasusedformodeinourexamplesthusfar.Becausethesharedpreferencesfilesarealwaysstoredwithinyourapplication’s/data/data/{package}directoryandthereforearenotaccessibletootherapplications,youonlyneedtouse

MODE_PRIVATE.

UsingDialogPreferenceSofar,you’veseenhowtousetheout-of-the-boxcapabilitiesofthepreferencesframework,butwhatifyouwanttocreateacustompreference?WhatifyouwantsomethingliketheslideroftheBrightnesspreferenceunderScreenSettings?ThisiswhereDialogPreferencecomesin.DialogPreferenceistheparentclassofEditTextPreferenceandListPreference.Thebehaviorisadialogthatpopsup,displayschoicestotheuser,andisclosedwithabuttonorviatheBackbutton.ButyoucanextendDialogPreferencetosetupyourowncustompreference.Withinyourextendedclass,youprovideyourownlayout,yourownclickhandlers,andcustomcodeinonDialogClosed()towritethedataforyourpreferencetothesharedpreferencesfile.

ReferenceHerearehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://developer.android.com/design/patterns/settings.htmlAndroid’sDesignGuidetoSettings.SomegoodadviceaboutlayingoutSettingsscreensandoptions.

http://developer.android.com/guide/topics/ui/settings.htmlAndroid’sAPIGuidetoSettings.ThispagedescribestheSettingsframework.

http://developer.android.com/reference/android/provider/Settings.htmlReferencepagethatliststhesettingsconstantsforcallingasystemsettingsactivity.

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforthefileProAndroid5_Ch11_Preferences.zip.ThisZIPfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribeshowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.

SummaryThischaptertalkedaboutmanagingpreferencesinAndroid:

Typesofpreferencesavailable

Readingthecurrentvaluesofpreferencesintoyourapplication

SettingdefaultvaluesfromembeddedcodeandbywritingthedefaultvaluesfromtheXMLfiletothesavedpreferencesfile

Organizingpreferencesintogroups,anddefiningdependenciesbetweenpreferences

Callbacksonpreferencestovalidatechangesandtosetdynamicsummarytext

Usingthepreferencesframeworktosaveandrestoreinformationfromanactivityacrossinvocations

Creatingacustompreference

Chapter12

UsingtheCompatibilityLibraryforOlderDevicesTheAndroidplatformhasgonethroughanimpressiveevolutionsinceitwasfirstintroducedseveralyearsago.WhiletheintentionhasalwaysbeenforAndroidtopowerlotsofdifferenttypesofdevices,itwasn’tarchitectedfromthebeginningtomeetthatgoal.Instead,theGoogleengineershaveadded,removed,andchangedAPIsinordertoprovidenewfeatures.OneofthebiggestchangeswasthecreationoffragmentsinordertohandlelargerscreensizessuchasontabletsandTVs.ButtherehavebeenotherchangessuchaswithActionBarandMenus.

ThenewAPIscreatedadifficultproblemfordeveloperswhowantedtheirapplicationstorunonthenewdeviceswiththenewAPIs,aswellasolderdevicesthatdidnothavethoseAPIs.ManyolderdevicesdonotgetAndroidupgrades.EvenifGoogleaddedthenewAPIstoarevisionoftheoldAndroidOS,theolddevicesaren’tgoingtogetthatnewrevision,becauseofthetestingandsupportrequiredfromboththedevicemanufacturerandthecellularcarrier.ThesolutionthatGooglecameupwithwastocreatecompatibilitylibrariesthatcouldbelinkedintoanapplicationsoitcouldtakeadvantageofthenewAPIfunctionalityyetstillrunonanolderversionofAndroid.ThelibraryfiguresouthowtousetheolderAPIstoimplementthenewfeatures.IfthesameapplicationrunsonanewerversionofAndroidthatalreadyhasthosenewfeatures,thecompatibilitylibrarycallsthroughtotheunderlyingAPIspresentinthatnewerversionofAndroid.

Thischapterwilldiveintothecompatibilitylibrariesandexplainhowtousethemandwhattowatchoutfor.Ifyouaren’tdevelopingapplicationsforolderversionsofAndroid,youcouldsafelyskipthischapterasyouwon’tneedthelibraries.ThelibrariesareonlyusefulifyouwanttoincludethefunctionalityofanewAPIinanapplicationthatwillrunonanoldversionofAndroidthatdoesn’thavethatnewAPI.

ItAllStartedwithTabletsTheAndroidoperatingsystemwasdoingfineuntilitcametimetosupporttablets.Thebasicbuildingblockofanapplicationwastheactivity,meanttoperformasingletaskfortheuserandtofillthescreenofthedevice.Buttabletsofferedmorerealestatesotheusercouldseeanddoafewthingsatatimeononescreen.SowithHoneycomb(Android3.0),Googleintroducedfragments.Thiswasawholenewconcept,whichchangedhowdeveloperscreatedUIsandthelogicthatranbehindthem.Andthiswouldhavebeenfine,exceptthattherewerestillplentyofAndroiddevices(e.g.,smartphones)inthewildwhichdidnotsupportfragments.WhatGooglefiguredoutisthatacompatibilitylibrarycouldbewrittentoprovidecomparableimplementationsofFragment,etc.,thatusedtheexistingAPIsintheolderversionsofAndroid.Ifanapplicationlinkedinthecompatibilitylibrary,

itcouldworkwithfragmentseventhoughtheolderversionofAndroiddidn’tsupportfragmentsintheOS.

TheGoogleengineersthenlookedatotherfeaturesandAPIsinnewAndroidandprovidedcompatibilitylibraryfeaturesandAPIstomatch,sothatthesefeaturescouldalsobeusedinolderversionsofAndroidwithouthavingtoreleaseupdatestothoseolderversionsofAndroid.InadditiontosupportforFragments,compatibilitylibrariesprovidesupportforLoaders,RenderScript,ActionBar,andothers.

Thecompatibilitylibrarydoesn’talwaysmakethingsperfectlythesamebetweenoldandnew.Forexample,thenewActivityclassisawareoffragments.Tousethecompatibilitylibrary,youmustextendtheFragmentActivityclassinsteadofActivity;itistheFragmentActivityclassthatworkswithfragmentsinoldAndroidversions.

Whenyouusethecompatibilitylibrary,youwillusethoseclassesforyourapplicationregardlessofwhichversionofAndroiditwillrunon.Inotherwords,youwouldonlyuseFragmentActivityinyourapplicationanditwilldotherightthinginallversionsofAndroid,includingAndroid3.0andlater.YouwouldnottrytoincludeinthesameapplicationbothActivityforAndroid3.0+andFragmentActivityforAndroidbelow3.0.WhenFragmentActivityisexecutingonAndroid3.0andabove,itcanprettymuchcallstraightthroughtotheunderlyingActivityclass.ThereisnorealpenaltytousingacompatibilitylibraryonarecentAndroidversion.

AddingtheLibrarytoYourProjectAsofthiswriting,therearefourcompatibilitylibraries;togetherthecollectioniscalledtheAndroidSupportLibrary,revision22.1.1:

v4—containsFragmentActivity,Fragment,Loader,andquiteafewotherclassesintroducedafterAndroid3.0.Thenumber4representsAndroidAPIversion4(i.e.,Donut1.6).ItmeansthislibrarycanbeusedforapplicationsthatrunonAndroidAPIversion4andabove.

v7—makesavailabletheActionBar,CardView,GridLayout,MediaRouter,PaletteandRecyclerViewclasses.ThislibrarycanbeusedwithAndroidAPIversion7(i.e.,Eclair2.1)andabove.Thereareactuallysixlibrarieshere:appcompat,cardview,gridlayout,mediarouter,paletteandrecyclerview

v8—addsRenderSciptcapabilitytoAndroidAPIversion8(i.e.,Froyo2.2)andabove.RenderScriptallowsforparallelizationofworkacrossdeviceprocessors(CPUcores,GPUs,DSPs)andwasintroducedinAndroidAPIversion11(i.e.,Honeycomb3.0).

v13—addssomespecialFragmentfunctionalityforthingsliketabbedandpagerinterfaces.Thislibraryalsocontainsmanyofthe

classesfromv4soitcanbeincludedinyourapplicationwithoutrequiringotherlibraries.

v17—addsLeanbackfeaturesrelatedtoAndroidTVapplications

Foracompletelistofallcompatibilityfunctionalitybyversionnumber,pleaseseethereferencesattheendofthischapter.

TodownloadtheAndroidSupportLibrarytoyourcomputer,usetheAndroidSDKManagerandlookforitatthebottomofthelistunderExtras.Ifyou’reusingAndroidStudio,downloadtheAndroidSupportRepository.Otherwise,downloadAndroidSupportLibraryinstead.ThefileswillbeplacedunderyourAndroidSDKdirectory.TheAndroidSupportLibrarycanbefoundinextras/android/support/,andtheAndroidSupportRepositorycanbefoundinextras/android/m2repository.

Asyoucanseefromtheprecedingbulletlist,notallfeaturesoftheAndroidSupportLibraryareavailableonallolderversionsofAndroid.Thereforeyoumustproperlysetandroid:minSdkVersioninyourAndroidManifest.xmlfile.Ifyouareusingacompatibilitylibraryfeaturefromv7,android:minSdkVersionshouldnotbelowerthan7.

Includingthev7SupportLibraryThere’sverylittlechancethatyou’deverwanttoincludethev4libraryandnotthev7library.Sincethev7libraryrequiresthatthev4libraryalsobeincludedtoprovidethenecessaryclassesforv7tofunctionproperly,you’llwanttoincludeboth.IfyouareusingEclipse,theADTplug-inmakesallofthisprettyeasy.WhenyoucreateanewAndroidprojectinEclipse,youspecifytheminimumversionofAndroidthatitwillrunon.IfADTthinksthatyoumightwantthecompatibilitylibraryincluded,itwillautomaticallyincludeit.

Forexample,ifyouspecifyatargetSDKof16(JellyBean4.1)butaminimumSDKof8(Froyo2.2),ADTwillautomaticallysetupanappcompatv7libraryproject,includethatlibraryprojectinyournewapplication,andalsoincludethev4libraryaswellinyourapplication.Theresourcesfromthev7libraryarethereforeavailabletoyourapplicationwithoutyouhavingtodoextrawork.However,ifyouwanttouseeitheroftheothertwov7libraries(gridlayoutand/ormediarouter),thosewillrequirealittleextrawork,aswillnowbeexplained.Bycreatingalibraryprojectandincludingthatinyourapplication,itwillincludethecompatibilitylibraryresourcesthatyourapplicationwillneed.

YouwillmanuallydosomethingsimilartowhatADTdidtoautomaticallyincludethev7appcompatlibraryintoyourproject.Tostart,youwillchooseFile Import,thenExistingAndroidCodeIntoWorkspace,thennavigatetotheextrasfolderwheretheAndroidSDKisonyourworkstation.Locatethev7gridlayoutormediarouterfolderandchoosethat.SeeFigure12-1.

Figure12-1.Importingthev7mediaroutercompatibilitylibrary

ClickFinishandyouwillgetanewlibraryproject.Ifyouchosetocreatealibraryprojectforv7mediarouter,youwillseethatitismissingsomefunctionalitysoithaserrors.Youneedtoaddinthev7appcompatlibrarytoclearthatup.Right-clickthemediarouterlibraryprojectinEclipseandchooseProperties.InthelistontheleftchooseAndroid.NowclicktheAdd…buttonintheLibrarysection.SeeFigure12-2.

Figure12-2.Addingappcompat_v7tothev7mediaroutercompatibilitylibrary

Selecttheappcompat_v7libraryandclickOK.Thatshouldclearuptheerrorsinmediarouter.Nowwhenyouwanttoincludemediarouterinyourapplicationproject,simplyfollowthesameprocedurebutright-clickyourapplicationproject,andwhenyouclicktheAdd…buttonforLibrary,chosethemediarouterlibrary.

WithAndroidStudio,addingav7compatibilitylibraryisjustaseasy.Bydefault,ifyoucreateanewprojectwithaminimumSDKvaluelessthanyourtargetSDK,youwillverylikelygetthev7appcompatlibraryaddedinautomatically.Youcancheckthisbylookingforthefollowinglineintheapp’sbuild.gradleconfigurationfileinthedependenciessection:compile'com.android.support:appcompat-v7:22.0.0'

Therefore,toaddoneoftheotherv7libraries,youwouldinsertanothersimilarcompilelinetothedependenciessection,butusetheappropriatenamesuchascardviewormediarouter.

Includingthev8SupportLibraryIfyouwanttousethev8renderscriptcompatibilitylibrary,andyoudevelopwithEclipse,yousimplyaddthefollowingthreelinestotheapplicationproject’sproject.propertiesfileregardlessofthetargetversionofyourapplication:

renderscript.target=22renderscript.support.mode=truesdk.buildtools=22.1.1

Atthetimeofthiswriting,theonlineAndroiddocumentationsaysthatyoushoulduseatargetof18andabuildtoolsof18.1.0.However,usingtheoldvaluesgeneratesanerror

sayingtouseanewerversionofbuildtools.IfyouseeerrorsintheEclipseConsoleregardingversionnumbers,tryusingalaterversionasindicatedbytheerror.

IfyoudevelopwithAndroidStudio,toincludev8renderscriptyouwouldedittheapp’sbuild.gradlefileandaddtheselineswithinthedefaultConfigsection:

renderscriptTargetApi22renderscriptSupportModeEnabledtrue

Withinyourcode,makesureyouimportfromandroid.support.v8.renderscriptratherthanandroid.renderscript.IfyouaremodifyinganexistingRenderScriptapplicationforthev8library,makesuretocleanyourproject;theJavafilesthataregeneratedfromyour.rsfilesneedtoberegeneratedtoalsousethev8library.YoucannowuseRenderScriptasusualanddeployyourapplicationtoolderversionsofAndroid.

Includingthev13SupportLibraryToincludethev13compatibilitylibraryintoyourapplicationusingEclipse,navigatetotheSDKextrasdirectoryandfindthev13jarfile.Copythisfiletothe/libsdirectoryofyourapplicationproject.Oncethev13jarfileisinplace,right-clickittopullupthemenu,andthenchooseBuildPath AddtoBuildPath.There’sagoodchanceyoualreadyhavethev4andv7appcompatlibrariesinyourapplicationcourtesyofADT.Youmaychoosetogetridofthoseifyoudon’tneedthefunctionalityfromeitherone.Forexample,iftheminimumSDKforyourapplicationisv11,youcanusethenativeActionBarclasswithouttheneedforthev7appcompatsupportlibrary.

Thev13jarfilecontainsmanyofthesameclassesasv4,soyoudon’twanttocauseanyproblemsbyhavingthesameclassesintwice.Ifyou’regoingtohaveallthreelibrariesinyourapplication(i.e.,v4,v7,andv13),thenatleastensurethatv13isorderedbeforev4.ThiscanbedoneintheConfigureBuildPathdialogbox.

Ifyou’reusingAndroidStudio,justmakesuretheSDKManagerhasdownloadedtheSupportRepository,thenaddthefollowingcompilelinetotheapp’sbuild.gradlefilejustlikeyoudoforv7libraries:

compile'com.android.support:support-v13:22.0.0'

Includingthev17SupportLibraryFinally,includingthev17compatibilitylibraryisdonethesamewayasforthev13supportlibrary.

IncludingJustthev4SupportLibraryIfyoureallymusthavethev4supportlibraryandnoneoftheothers,youwouldfollowthesameprocedureasforthev13library.

RetrofittinganAppwiththeAndroidSupportLibraryTogetabetterfeelforhowthisallworks,you’regoingtobringbackafragmentappyouworkedoninChapter8andwillmakeitworkforolderversionsofAndroidthatdon’tnativelysupportfragments.

UseFile Import,chooseGeneral,thenExistingProjectsintoWorkspace.NavigatetotheShakespeareInstrumentedprojectfromChapter8andchoosethat.Check“Copyprojectsintoworkspace”beforehittingFinish.

Nowyou’regoingtoretrofitthisapplicationtoworkonversionsofAndroidlowerthanAPIversion11.Thefollowingworkswhenyoudon’tneedresourcesfromthecompatibilitylibrary,sinceitworriesonlyaboutcopyingintheJARfile.

1. Right-clickyourprojectandchooseAndroidTools AddSupportLibrary….AcceptthelicenseandclickOK.

2. NowgointoMainActivity.javaandchangethebaseclassfromActivitytoFragmentActivity.Youneedtofixtheimportlinefromandroid.app.Activitytoandroid.support.v4.app.FragmentActivity.AlsofixtheimportsforFragment,FragmentManager,andFragmentTransactiontousetheonesfromthesupportlibrary.

3. FindthemethodcallsforgetFragmentManager()andchangethesetogetSupportFragmentManager().DothisalsoforDetailsActivity.java.

4. ForDetailsFragment.java,changetheimportforFragmenttotheoneforthesupportlibraryFragment(i.e.,android.support.v4.app.Fragment).

5. InTitlesFragment.java,changetheimportforListFragmenttotheoneforthesupportlibraryListFragment(i.e.,android.support.v4.app.ListFragment).

ThenewerversionsofAndroidusedifferentanimatorsfromoldAndroid.YoumayneedtofixanimationsinMainActivity.javaintheshowDetails()method.PickoneofthecommentedoutcallstosetCustomAnimations(),thenplaywiththeinandoutanimations.AnythingthatreliesonanObjectAnimatorclasswillnotworkonolderdevicessincethisclasswasintroducedwithAPIversion11(i.e.,Honeycomb3.0).ItwillcompilebutsincethatclasshasnotbeenimplementedinolderAndroidandhasnotbeenincludedinthecompatibilitylibraries,youwillgetaruntimeexception.Inotherwords,avoidR.animator.TryusingR.animinstead.Youcancopyintoyourprojectanim

resourcefilesthatyou’dliketouse,oryoucantryreferringtoandroid.R.animfiles.

NowyoucangointoAndroidManifest.xmlandchangetheminSdkVersionfrom11to8.Thatshouldbeallyouneedtodo.TryrunningthisapplicationonaFroyodeviceoremulator.Ifallwentwellyoushouldnowbeseeingafragment-basedapplicationrunningonapre–Android3.0OS.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://developer.android.com/tools/support-library/index.html:TheAndroidDeveloper’sGuideontheSupportLibrarypackage.

http://developer.android.com/tools/support-library/features.html:Androiddocumentationonthemainfeaturesofeachcompatibilitylibrary.

http://developer.android.com/tools/support-library/setup.html:Androiddocumentationonsettingupacompatibilitylibraryforyourproject,forbothEclipseandAndroidStudio.Atthetimeofthiswriting,thesepageswerenotascurrentasthischapter.However,thingschange.Ifyouexperiencetrouble,checktheonlinedocumentationorcontactthebook’sauthors.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouttheAndroidcompatibilitylibraries:

Togetyourapplicationworkingonthebroadestarrayofdevices,usethecompatibilitylibrariesandcodetotheirAPIsratherthanthelatestandgreatestAPIs.

Thev7supportlibrariescomewithresourcesthatmustbeincludedinyourapplicationfortheAPIstoworkproperly.

Chapter13

ExploringPackages,Processes,Threads,andHandlersInthebookthusfar,wehavefocusedontheessentialsofhowtoprogramfortheAndroidplatform.InthischapterwewanttogounderthehoodabittoaddresstheprocessandthreadingmodelforAndroidprograms.Thisdiscussionwillleadustosigningpackages,sharingdatabetweenpackages,usingcompile-timelibraries,thenatureofAndroidcomponentsandhowtheyusethreads,andfinallytheneedforhandlersandhowonecancodehandlers.

Asyougothroughthischapter,keepinmindthattheword“package”isoverloaded.SometimesitreferstotheJavalanguagepackage,andsometimesitreferstotheAPKfilesthatAndroidapplicationsaredeployedas.

UnderstandingPackagesandProcessesWewillstartwithAndroidpackagesandtheprocessmodel.WhenyoudevelopanapplicationinAndroid,youendupwithan.apkfile.Yousignthis.apkfileanddeployittothedevice.Each.apkfileisuniquelyidentifiedbyauniquejava-language-stylepackagename,asshowninthemanifestfileshowninListing13-1.

Listing13-1.ProvidingaPackageNameintheManifestFile

<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.testapp"...>...restofthexmlnodes</manifest>

Ifyouwerethedeveloperofthispackage,nooneotherthanyoucouldupdatethisapplicationonceitisdeployed.TheAndroidapplicationpackagenameisreservedforyou.Thistie-uphappenswhenyousignandregisteryourappwithvariousapppublishers.SochoosethisAndroidapplicationpackagenameverysimilartothewaythatJavapackagesarenamed.Thisneedstobeuniqueintheworld.Onceyoupublishtheapp,youcannotchangethispackagename,asthisdefinesyourapplication’sidentity.

Androidusesthepackagenameasthenameoftheprocessthatrunsthecomponentsofthispackage.AndroidalsoallocatesauniqueuserIDforthisprocesstorununder.ThisuserIDisessentiallytheuserIDfortheunderlyingLinuxOS.AsthisuserIDisdeterminedatthetimeoftheinstallonaparticulardevice,itwillbedifferentoneachdevicewhereyourappisinstalled.Youcandiscoverthisinformationbylookingatthe

detailsoftheinstalledpackagethroughthedevelopertoolsintheAndroidEmulator.Forexample,apackagedetailscreenfortheinstalledbrowserapplicationlookslikeFigure13-1.(Pleasenotethatthisimageortoolwhereyoulookthisupmayvaryfromreleasetorelease.TheimageinFigure13-1istakenfromthedevelopertoolsapplicationontheAndroidEmulator.)

Figure13-1.Androidpackagedetails

Figure13-1showsthenameoftheprocessasindicatedbytheJavapackagenameinthemanifestfileandtheuniqueuserIDallocatedtothispackage.AnyresourcescreatedbythisprocessorpackagewillbesecuredunderthatLinuxuserID.Thisscreenalsoliststhecomponentsinsidethispackage.Examplesofcomponentsareactivities,services,andbroadcastreceivers.DonotethatthisimagemayvarydependingontheAndroidrelease.Throughthesettingsofthedeviceortheemulator,youcanalsouninstallthepackagesothatitcanberemoved.

Becauseaprocessistiedtoapackagename,andapackagenameistiedtoitssignature,signaturesplayaroleinsecuringthedatabelongingtoapackage.Apackageistypicallysignedwithaself-signedPKI(PublicKeyInfrastructure)certificate.Acertificate

identifieswhotheauthorofthepackageis.Thesecertificatesneednotbeissuedbyacertificateauthority.Thismeanstheinformationinthecertificateisnotapprovedorvalidatedbyanyauthority.ThismeansonecancreateacertificatethatsaysthattheirnameisGoogle.Theonlyassuranceisthatthispackagenameisreservedtothatuserifnoonehadclaimeditinthemarketplacebefore,andanysubsequentupdatestothatpackagearegivenonlytothatuser(identifiedbythatcertificate).AllassetsthatareinstalledorcreatedthroughthispackagebelongtotheuserwhoseIDisassignedtothepackage.Ifyourintentionistoallowasetofcooperatingapplicationsthatdependonacommonsetofdata,youhaveanoptiontoexplicitlyspecifyauserIDthatisuniquetoyouandcommonforyourneeds.ThisshareduserIDisalsodefinedinthemanifestfile,similartothedefinitionofapackagename.Listing13-2showsanexample.

Listing13-2.SharedUserIDDeclaration

<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.somepackage"sharedUserId="com.androidbook.mysharedusrid"...>...therestofthexmlnodes</manifest>

MultipleapplicationscanspecifythesameshareduserIDiftheysharethesamesignature(signedwiththesamePKIcertificate).HavingashareduserIDallowsmultipleapplicationstosharedataandevenruninthesameprocess.ToavoidtheduplicationofashareduserID,useaconventionsimilartonamingaJavaclass.HerearesomeexamplesofshareduserIDsfoundintheAndroidsystem:

"android.uid.system""android.uid.phone"

NoteAsharedIDmustbespecifiedasarawstringandnotastringresource.

Asanoteofcaution,ifyouareplanningtouseshareduserIDs,therecommendationistousethemfromthestart.Otherwise,theydon’tworkwellwhenyouupgradeyourapplicationfromanonshareduserIDtoonewithasharedID.OneofthecitedreasonsisthatAndroidwillnotrunchownontheoldresourcesbecauseoftheuserIDchange.

ACodePatternforSharingDataThissectionexplorestheopportunitieswhentwoapplicationswanttoshareresourcesanddatathroughtheuseofashareduserID.Theresourcesanddataofeachpackageareownedandprotectedbythatpackage’scontextduringruntime.Youneedaccesstothecontextofthepackagefromwhichyouwanttosharetheresourcesordata.

YoucanusethecreatePackageContext()APIonanyexistingcontextobject(such

asyouractivity)togetareferencetothetargetcontextthatyouwanttointeractwith.Listing13-3providesanexample.

Listing13-3.UsingthecreatePackageContext()API

//Usetheappropriatetry/catchtodetecterrors//IdentifypackageyouwanttouseStringtargetPackageName="com.androidbook.samplepackage1";

//Decideonanappropriatecontextflagintflag=Context.CONTEXT_RESTRICTED;

//Getthetargetcontextthroughoneofyouractivities//NeedtocatchNameNotFoundExceptionActivitymyContext=......;ContexttargetContext=myContext.createPackageContext(targetPackageName,flag);

//UsecontexttoresolvefilepathsResourcesres=targetContext.getResources();Filepath=targetContext.getFilesDir();

Noticehowweareabletogetareferencetothecontextofagivenpackagenamesuchascom.androidbook.samplepackage1.ThistargetContextinListing13-3isidenticaltothecontextthatispassedtothetargetapplicationwhenthatapplicationislaunched.Asthenameofthemethodindicates(inits“create”prefix),eachcallreturnsanewcontextobject.However,thedocumentationassuresusthatthisreturnedcontextobjectisdesignedtobelightweight,meaningitdoesn’tconsumealotofmemoryandisoptimizedtoreferthetargetpackage’sresources,assets,andcode.

ThisAPIisapplicableregardlessofwhetherbothcontextsshareauserID.IfyousharetheuserID,itiswellandgood.Ifyoudon’tshareauserID,thetargetapplicationwouldneedtodeclareitsresourcesaccessibletooutsideusers.

TheCONTEXT_RESTRICTEDflagindicatesthatyouareinterestedinjustloadingtheresourcesandtheassetsandnotthecode.Sousingthisflagallowsthesystemtodetectifthelayoutscontainreferencestocallbackcode.Exampleofacallbackwouldbeabuttoninalayoutreferringtoamethodthatwouldbecalled.Thiscallbackcodeexistsinthesourcecontext.So,youwouldwantthesystemtothrowanexceptionsothatyoucandetectthatconditionorignorethatparticularXMLtag.Inessence,youaretellingthesystemthatyouareusingthecontextinarestrictedsenseandthetargetcontextisfreetomakesuitableassumptionsbasedonthatflag.Thebottomlineappearstobethatifyouareinterestedinnotusingthecodefromthetargetcontext,usethisflag.

CONTEXT_INCLUDE_CODEallowsyoutoloadJavaclassesatruntimefromthetargetcontextintoyourprocessandcallthatcode.Documentationindicatesthatyoumayreceiveasecurityexceptionifitisnotsafetoloadthecode.However,itisnotclearunderwhatcircumstancesthecodeisconsideredunsafe.Oneeducatedguessisthatthetarget

contextdoesnothaveashareduserIDasthatofthesourcecontext.YoucanovercomethisrestrictionbyalsospecifyingtheCONTEXT_IGNOR_SECURITYalongwiththeCONTEXT_INCLUDE_CODE.Thesetwoflagstogetherloadthetargetcontextcodeintothesourcecontextcodeallthetime,ignoringevenifthetargetcontextbelongstoadifferentuser.Althoughcodeisborrowedandrunsintheclientprocess,itwillnothavepermissionstothetargetcontextdata.So,besurewhatthatcodedoeswhenletlooseonyourdata.Thisapproachisoftenusedforutilitycodethatcanbeshared.

UnderstandingLibraryProjectsAswetalkthroughsharingcodeandresources,onequestionworthaskingis,willtheideaofa“library”projecthelp?StartingwithADT0.9.7Eclipseplug-in,Androidsupportstheideaoflibraryprojects.Theapproachtobuildinglibrarieshasbeenchangingabitsincethen,whilethecentralidearemainsinallrecentreleases.

AlibraryprojectisacollectionofJavacodeandresourcesthatlookslikearegularAndroidprojectbutneverendsupinan.apkfilebyitself.Instead,thecodeandresourcesofalibraryprojectbecomepartofanotherprojectandgetcompiledintothatmainproject’s.apkfile.Aslibrariesarepurelyacompile-timeconcept,eachdevelopmenttoolmaycraftthisfacilitydifferently.

Herearesomeadditionalfactsabouttheselibraryprojects:

Alibraryprojectcanhaveitsownpackagenamedistinctfromthemainapplication.

AlibraryprojectcanuseotherJARfiles.

EclipseADTwillcompilethelibraryJavasourcefilesintoaJARfilethatisthencompiledwiththeapplicationproject.

ExceptfortheJavafiles(whichbecomeajarfile),therestofthefilesbelongingtoalibraryproject(suchasresources)arekeptwiththelibraryproject.Thepresenceofthelibraryprojectisrequiredinordertocompiletheapplicationprojectthatincludesthatlibraryasadependency.

StartingwithSDKTools15.0,theresourceIDsgeneratedforlibraryprojectsintheirrespectiveR.javafilesarenotfinal.(Thisisexplainedlaterinthechapter.)

BoththelibraryprojectandthemainprojectcanaccesstheresourcesfromthelibraryprojectthroughtheirrespectiveR.javafiles.ThismeanstheIDnamesareduplicatedandavailableinbothR.javafiles.

IfyouwouldliketodistinguishresourceIDsbetweenthetwoprojects(libraryandmain),youcanusedifferentresourceprefixes,suchaslib_forthelibraryprojectresources.

Amainprojectcanreferenceanynumberoflibraryprojects.

Components,suchasanactivity,ofalibraryneedtobedefinedinthetargetmainprojectmanifestfile.Whenthisisdone,thecomponentnamefromthelibrarypackagemustbefullyqualifiedwiththelibrarypackagename.

Itisnotnecessarytodefinethecomponentsinalibrarymanifestfile,althoughitmaybeagoodpracticetoknowquicklywhatcomponentsitsupports.

CreatingalibraryprojectstartswithcreatingaregularAndroidprojectandthenchoosingtheIsLibraryflaginitspropertieswindow.

Youcansetthedependentlibraryprojectsforamainprojectthroughtheprojectpropertiesscreenaswell.

Clearly,beingalibraryproject,anynumberofmainprojectscanincludealibraryproject.

Onelibraryprojectcannotreferenceanotherlibraryprojectasofthereleases(Android4.4,API19,SDKTools19,ADT22.3),althoughthereseemstobeadesiretobeabletodosoinfuturereleases.

Tocreatealibraryproject,youstartbycreatingaregularAndroidproject.Oncetheprojectissetup,right-clicktheprojectnameandclickthepropertiescontextmenutoshowthepropertiesdialogforthelibraryproject.ThisdialogisshowninFigure13-2.(TheavailablebuildtargetsinthisfiguremayvarywithyourversionoftheAndroidSDK.)SimplyselectIsLibraryfromthisdialogtosetupthisprojectasalibraryproject.

Figure13-2.Designatingaprojectasalibraryproject

Youcanusethefollowingprojectpropertiesdialog(seeFigure13-3)toindicatethatamainprojectdependsonthelibraryprojectthatwascreatedearlier.

Figure13-3.Declaringalibraryprojectdependency

NoticetheAddbuttoninthedialog.YoucanusethistoaddthelibraryinFigure13-3asareference.Youdon’tneedtodoanythingelse.

Oncethelibraryprojectissetupasadependencyforthemainapplicationproject,thelibraryprojectappearsasacompiledJARfileintheapplicationprojectunderthenodeAndroidDependencies.

Androiddoesn’tpackageR.classfilesfromthelibrariesintheirrespectivejarfiles.Instead,itreliesonthesourceR.javafilethatisre-createdandmadeavailableinthemainapplicationprojectforeachofthelibraries.ThatmeansyouhaveanR.javafileforeachofthelibrariesinthegensubdirectoryofthemainproject.

Toavoidhard-codedconstantsbeinginthecompiledsourcecodeofthelibraries,AndroidcreatesthelibraryR.javafilessuchthatalltheconstantsinthatfilearenon-final.Duringthefinalcompilationofthemainproject,newconstantvaluesareallocatedsothattheseconstantvaluesareuniqueacrossallthelibrariesandthemainproject.Hadwegivenfinalconstantvaluesduringlibrarycompilation,thenthosenumberscouldcollidebetweenlibraries.AllocationofIDsuniquelyforagivensetofnamesmustbedoneonetime.OncethesenumbersareallocatedtotheIDsduringthecompileofthemainproject,theycan

becomefinalinthatmainproject.

ThereisanimplicationtiedtothefactthatIDsinthelibrary’sR.javafilearenotfinal.ItiscommontouseaswitchstatementtorespondtomenuitemsbasedonamenuitemID.ThislanguageconstructwillfailatcompiletimewhendoneinthelibrarycodeiftheIDsarenotfinal.Thisisbecausethecasestatementinaswitchclausehastobeanumericalconstantnumber.

So,theswitchstatementinListing13-4willnotcompileunlesstheIDs(suchasR.id.menu_item_1)areactualliteralnumbersorstaticfinals.

Listing13-4.SampleswitchStatementtoDemonstrateNon-FinalVariables

switch(menuItem.getItemId()){caseR.id.menu_item_1:Statement1;break;case0x7778888://asanexampleforR.id.menu_item_2:statement;statement;break;default:statement;statement;}

BecausetheIDsaredefinedasnon-finalforlibraryprojects,weareforcedtouseif/elsestatementsinsteadofswitch/caseclauses.Becausethesameconstantsre-createdfromthelibrary’sR.javafilesarefinal,youcanusefreelytheswitchclauseinyourfinalproject.

Asyoucansee,libraryprojectsarecompile-timeconstructs.Clearly,anyresourcesthatbelongtothelibrarygetabsorbedandmergedintothemainproject.Thereisnotaquestionofsharingatruntime,becausethereisjustonepackagefilewiththenameofthemainpackage.Inshort,librariesofferawaytoshareresourcesbetweenrelatedprojectsatcompiletime.

UnderstandingComponentsandThreadsWestartedoffthischapterestablishingthateachpackagerunsinitsownprocess.Wewillnowexplaintheorganizationofthreadswithinthisprocess.Thiswillleadustowhyweneedhandlerstooffloadtheworkfromthemainthreadandalsotocommunicatewiththemainthread.

MostcodeinanAndroidapplicationrunsinthecontextofacomponentsuchasanactivityoraservice.Mostofthetime,thereisonlyonethreadrunninginanAndroidprocess,calledthemainthread.Wewilltalkabouttheimplicationsofsharingthismainthreadamongvariouscomponents.Primarily,thiscanleadtoApplicationNotResponding

(ANR)messages(the“A”standsfor“application”andnot“annoying”).Wewillshowyouhowyoucanusehandlers,messages,andthreadstobreakthedependencyonthemainthreadwhenlong-runningoperationsareneeded.

AnAndroidprocesshasfourprimarycomponenttypes:Activity,Service,ContentProvider,andaBroadcastReceiver.MostcodeyouwriteinanAndroidapplicationispartofoneofthesecomponentsorcalledbyoneofthesecomponents.EachofthesecomponentsgetsitsownXMLnodeunderanapplicationnodespecificationintheAndroidprojectmanifestfile.Torecall,herearethesenodesinListing13-5:

Listing13-5.HowComponentsAreDeclaredintheManifestFile

<manifest…><application><activity/><service/><receiver/><provider/></application></manifest>

Withsomeexceptions(suchasexternalprocesscallstocontentproviders),Androidusesthesamethreadtoprocess(orrunthrough)codeinthesecomponents.Thisthreadiscalledthemainthreadoftheapplication.Whenthesecomponentsarecalled,thecallcanbeeitherasynchronouscall,suchaswhenyoucallacontentproviderfordata,oradeferredonethroughamessagequeue,suchaswhenyouinvokefunctionalitybycallingastartserviceorshowadialog.

Figure13-4describestherelationshipbetweenthreadsandthesefourcomponents.ThisdiagramshowshowthreadsweavethroughtheAndroidframeworkanditscomponents.Thediagramdoesnotindicatetheorderinwhichathreadmightweavethroughthevariouscomponents.Thediagramismerelyshowingthattheprocessingcontinuesfromonecomponenttoanotherinasequentialfashion.

Figure13-4.Androidcomponentsandthreadingframework

AsindicatedinFigure13-4,themainthreaddoestheheavylifting.Itrunsthroughallthecomponentsbyusingamessagequeue.Asyouselectmenusorbuttonsonthedevicescreen,thedevicewilltranslatetheseactionsasmessagesanddropthemontothemainqueueoftheprocessthatisinfocus.Themainthreadsitsinaloopandprocesseseachmessage.Ifanymessagetakesmorethanfivesecondsorso,AndroidthrowsanANRmessage.

Similarly,inresponsetoamenuitem,ifyouweretoinvokeabroadcastmessage,Androidagaindropsamessageonthemainqueueofthepackageprocessfromwhichtheregisteredreceiveristobeinvoked.Themainthreadwillcomearoundtothatmessageatalatertimetoinvokethereceiver.Themainthreaddoestheworkforabroadcastreceiveraswell.Ifthemainthreadisbusyrespondingtoamenuaction,thebroadcastreceiverwillhavetowaituntilthemainthreadgetsfreedup.

Thesameistruewithaservice.WhenyoustartalocalservicewithActivity.startServicefromamenuitem,amessageisdroppedontothemainqueue,andthemainthreadwillcomearoundtoprocessitviatheservicecode.

Callstoalocalcontentproviderareslightlydifferent.Acontentproviderstillrunsonthemainthreadforalocalcall,butacalltoitissynchronousanddoesnotusemessagequeues.

Youmayask,“WhyisitimportantwhethermostcodeinanAndroidapplicationrunsonthemainthreadorotherwise?”ThisisimportantbecausethemainthreadhastheresponsibilitytogetbacktoitsqueuesothatUIeventsarerespondedto.Asaconsequence,youshouldnotholdupthemainthread.Ifthereissomethingthatisgoingto

takelongerthanfiveseconds,youshouldgetthatdoneinaseparatethreadordeferitbyaskingthemainthreadtocomebacktoitwhenitisfreedupfromotherprocessing.Whenexternalclientsorcomponentsoutsideoftheprocessmakeacalltothecontentproviderfordata,thenthatcallisallocatedathreadfromathreadpool.Thesameistruewithexternalclientsconnectingtoservices.

Let’slookatwhathandlersareandhowtheyfunctioninthenextsection.

UnderstandingHandlersWehavebrieflyreferredtotheideaofdeferringworkonamainthreadifneeded.Thisisdonethroughhandlers.HandlersareextensivelyusedthroughoutAndroidsothatthemainUIthreadisnotheldup.Theyalsoplayaroleincommunicatingwiththemainthreadfromotherspawnedworkerthreads.

Ahandlerisamechanismtodropamessageonthemainqueue(moreprecisely,thequeueattachedtothethreadonwhichthehandlerisinstantiated)sothatthemessagecanbeprocessedatalaterpointintimebythatcirculatingthread.Themessagethatisdroppedhasaninternalreferencepointingtothehandlerthatdroppedit.

Whenthemainthreadgetsaroundtoprocessingthatmessage,itinvokesthehandlerthatdroppedthemessagethroughacallbackmethodonthehandlerobject.ThiscallbackmethodiscalledhandleMessage.Figure13-5presentsthisrelationshipbetweenhandlers,messages,andthemainthread.

Figure13-5.Handler,message,messagequeuerelationship

Figure13-5illustratesthekeyplayersthatworktogetherwhenwetalkabouthandlers:mainthread,mainthreadqueue,handler,andamessage.Outofthesefour,wearenotexposedtothemainthreadorthequeuedirectly.Weprimarilydealwiththehandlerobjectandthemessageobject.Evenbetweenthesetwo,thehandlerobjectcoordinatesmostofthework.

Althoughahandlerallowsustodropamessageontothequeue,itisthemessageobjectthatactuallyholdsareferencebacktothehandler.Themessageobjectalsoholdsadatastructurethatcanbepassedbacktothehandler.

Workingwithahandlerandmessagesisbestunderstoodthroughanexample.Fortheexample,wewillhaveamenuitemthatinvokesafunction,andthatfunction,inturn,performsanactionfivetimesatone-secondintervalsandreportsbacktotheinvokingactivityeachtime.

Ifwedidn’tmindholdingupthemainthread,wecouldhavecodedthisscenariolikethepseudocodeinListing13-6.

Listing13-6.HoldingUptheMainThreadwithaSleepMethod

publicclassSomeActivity{....othermethodsvoidrespondToMenuItem(){//ProvethatweareonthemainthreadUtils.logThreadSignature();//simulateanoperationthattakeslongerthan5secondsfor(inti=0;i<6;i++){sleepFor(1000);//putmainthreadtosleepfor1secdosomething();SomeTextView.setText("didsomething.Counter:"+Integer.toString(i));}}}

Thiswillsatisfytherequirementoftheusecase.However,ifwedothis,weareholdingupthemainthread,andweareguaranteedtohaveanANR.WecanuseahandlertoavoidtheANRinthepreviousexample.PseudocodetodothisviaahandlerwilllooklikeListing13-7.

Listing13-7.InstantiatingaHandlerfromtheMainThread

voidrespondToMenuItem(){SomeHandlerDerivedFromHandlermyHandler=newSomeHandlerDerivedFromHandler();myHandler.doDeferredWork();//invokeafunctionin1secintervals//notethatdoDeferredWork()isnotpartoftheSDK//wewillshowyouthecodeforthisshortly}

Now,thecallrespondToMenuItem()willallowthemainthreadtogobacktoitsloop.Theinstantiatedhandlerknowsthatitisinvokedonthemainthreadandhooksitselfuptothequeue.ThemethoddoDeferredWork()willscheduleworksothatthemainthreadcangetbacktothisworkonceitisfree.

Toinvestigatethisprotocol,let’sseetheactualsourcecodeforaproperhandler.ThecodeinListing13-8inthenextsectiondemonstratesthishandler,whichiscalledDeferWorkHandler.InthepreviouspseudocodeofListing13-7,theindicatedhandlerSomeHandlerDerivedFromHandlerisequivalenttothisDeferWorkHandler.Similarly,theindicatedmethoddoDeferredWork()(ofListing13-7)isimplementedontheDeferWorkHandlerinListing13-8.

Listing13-8.DeferWorkHandlerSourceCode

publicclassDeferWorkHandlerextendsHandler{//Keeptrackofhowmanytimeswesentthemessageprivateintcount=0;

//Aparentdriveractivitywecanusetoinformofstatus.privateTestHandlersDriverActivityparentActivity=null;

//Duringconstructionwetakeintheparentdriveractivity.publicDeferWorkHandler(TestHandlersDriverActivityinParentActivity){parentActivity=inParentActivity;}//Callbackmethodthatgetscalledbythemainthread

@OverridepublicvoidhandleMessage(Messagemsg){

//UsethemessageobjecttogettoitsdataStringpm=newString("messagecalled:"+count+":"+msg.getData().getString("message"));//youcanaccesstheparentactivityandinvokeUIcallsonithereparentActivity.someControl.somemethod();//exampleonly

//logictoinvokeitselfmultipletimesifneededif(count>5){return;}count++;//incrementcountsendTestMessage(1);//reinvokeagainbysendingamessage}//methodcalledbytheclient

publicvoiddoDeferredWork(){count=0;sendTestMessage(1);}//PreparingandsendingthemessagepublicvoidsendTestMessage(longinterval){Messagem=this.obtainMessage();prepareMessage(m);this.sendMessageDelayed(m,interval*1000);}publicvoidprepareMessage(Messagem){Bundleb=newBundle();

b.putString("message","HelloWorld");m.setData(b);return;}}

Let’slookatthekeyaspectsofthissourcecode.Thefirstisthatthehandlerisderivedfromthebaseclasshandler.Intheconstructorofthehandler,weuseapointertotheparentactivitysothatwecanusetheUIcontrolsoftheactivitytoreportwhatneedstobereportedortoactupon.Thenwecodeamethod(doDeferredWork)toencapsulatewhatthishandlerisexpectedtodoforus.NoticethatthedoDeferredWork( )isnotanoverriddenmethodandyoucancallthismethodwhatevernamethatyouwouldlike.ItisinthismethodthatyouworkwithmessagestoeventuallycalltheoverriddenhandleMessage( ).Also,itisinthishandleMessage( )thatyouactuallyputtherealcodethatisoriginallydeferredfromthemainthread.

Thebasehandleroffersaseriesofmethodstosendmessagestothequeuetoberespondedtolater.ThesemethodsareusedinthedoDeferredWork( ).sendMessage()andsendMessageDelayed()aretwoexamplesofthesesendmethods.sendMessageDelayed(),whichweusedintheexample,allowsustodropamessageonthemainqueuewithagivenamountoftimedelay.sendMessage(),incontrast,dropsthemessageimmediatelytobeprocessedwhenthemainthreadgetsaroundtoit.

WhenyoucallsendMessage()orsendMessageDelayed(),youwillneedaninstanceofthemessageobject.Itisbestthatyouaskthehandlertogiveittoyou,becausewhenthehandlerreturnsthemessageobject,ithidesitselfinthebellyofthemessage.Thatway,whenthemainthreadcomesalong,itknowswhichhandlertocallbasedsolelyonthemessage.InListing13-8,themessageisobtainedusingthefollowingcode:

Messagem=this.obtainMessage();

Thevariablethisreferstoisthehandlerobjectinstance.Asthenameindicates,themethoddoesnotcreateanewmessagebutinsteadgetsonefromaglobalmessagepool.Atalaterpoint,oncethismessageisprocessed,itwillberecycled.ThemethodobtainMessage()hasthevariationsshowninListing13-9.

Listing13-9.ConstructingaMessageThroughaHandler

obtainMessage();obtainMessage(intwhat);obtainMessage(intwhat,Objectobject);obtainMessage(intwhat,intarg1,intarg2)obtainMessage(intwhat,intarg1,intarg2,Objectobject);

Eachmethodvariationsetsthecorrespondingfieldsonthemessageobject.Therearesomerestrictionsontheobjectargumentwhenthemessagecrossesprocessboundaries.Insuchcases,itneedstobeParcelable.Itismuchsaferandcompatibleinsuchcasesto

usethesetData()methodexplicitlyonthemessageobject,whichtakesaBundle.InListing13-8,wehaveusedsetData().Youareencouragedtousearg1orarg2insteadifwhatyouareintendingtopassaresimpleindicatorsthatcanbeaccommodatedwithintegervalues.

Theargumentwhat(inListing13-9)allowsyoutodequeuemessageorenquireiftherearemessagesofthistypeinthequeue.Seetheoperationsonthehandlerclassformoredetails.

Onceweobtainamessagefromthehandler,wecanoptionallymodifythedatacontentsofthatmessage.Inourexample,wehaveusedthesetData()functionbypassingitaBundleobject.Afterwehavecategorizedoridentifiedthedataofthemessage,wecansendthemessagetothequeuethroughsendMessage()orsendMessageDelayed().Whenthesemethodsarecalled,themainthreadwillreturntoattendingthequeue.

Oncethemessagesaredeliveredtothequeue,thehandlersitsandwaits(figurativelyspeaking)untilthemainthreadretrievesthosemessagesandcallsthehandler’shandleMessage().

Ifyouwanttoseethishandlerandmainthreadinteractionmoreclearly,youcanwritealogcatmessagewhenyouaresendingthemessageandinthehandleMessage()callback.YouwillnoticethetimestampsdifferasthemainthreadwouldhavetakenafewmoremillisecondstocomebacktothehandleMessage()method.

Inourexample,eachhandleMessage(),afterprocessingonemessage,sendsanothermessagetothequeuesothatitcanbecalledagain.Itdoesthisfivetimes,andwhenthecounterreachesfive,itquitssendingmessagestothequeue.Thisisonewaytobreakuptheworkintomultiplechunks,althoughtherearebetterwaystodothiseitherthroughaworkerthreadorthroughaclassAsyncTask.TheessentialAsyncTaskiscoveredinthenextchapter.Let’scovertheexplicitworkerthreadsoptionbrieflynow.

UsingWorkerThreadsWhenweuseahandlerliketheoneintheprevioussection,thecodeisstillexecutedonthemainthread.EachcalltohandleMessage()stillshouldreturnwithinthetimestipulationsofthemainthread(inotherwords,eachmessageinvocationshouldcompleteinlessthanfivesecondstoavoidApplicationNotResponding).Ifyourgoalistoextendthattimeofexecutionfurther,youwillneedtostartaseparatethread,keepthethreadrunninguntilitfinishesthework,andallowforthatsubthreadtoreportbacktothemainactivity,whichisrunningonthemainthread.Thistypeofasubthreadisoftencalledaworkerthread.

Itisano-brainertostartaseparatethreadwhilerespondingtoamenuitem.However,theclevertrickistoallowtheworkerthreadtopostamessagetothequeueofthemainthreadthatsomethingishappeningandthatthemainthreadshouldlookatitwhenitgetstothatmessage.ItisalsoanerrortocallUImethodsonanon-UIthread.So,youwillneedthis

handlerthatistiedtothemainthreadtocallUImethodsfromaworkerthread.

Areasonablesolutionthatinvolvesaworkerthreadisasfollows:

1. Createahandlerinthemainthreadwhilerespondingtothemenuitem.Keepitaside.

2. Createaseparatethread(aworkerthread)thatdoestheactualwork.Passthehandlerfromstep1totheworkerthread.Thishandlerallowstheworkerthreadtocommunicatewiththemainthread.

3. Theworkerthreadcodecannowdotheactualworkforlongerthanfivesecondsand,whiledoingit,cancallthehandlertosendstatusmessagestocommunicatewiththemainthread.

4. Thesestatusmessagesnowgetprocessedbythemainthread,becausethehandlerbelongedtothemainthread.Themainthreadcanprocessthesemessageswhiletheworkerthreadisdoingitswork.

Youcanseethesamplecodeforthisinteractioninthedownloadableprojectforthischapter.AnalternateandprobablymorestraightforwardwaytocommunicatewiththeUIthreadfromaworkerthreadistogetholdoftheactivitypointerandcallthemethodActivity.runOnUiThread(Runnableaction).OfcourseyouneedtocreateaRunnableobjectforcoordination.

ReferencesHerearesomeusefullinkstofurtherstrengthenyourunderstandingofthischapter:

http://developer.android.com/guide/publishing/app-signing.html:Amust-readforsigning.apkfiles.

http://developer.android.com/guide/developing/projects/projects-eclipse.html:PrimarySDKreferenceforAndroidlibraries.

http://developer.android.com/guide/topics/fundamentals.htmlSDKreferenceonAndroidcomponentlifecycles.

http://www.androidbook.com/item/3493:Alayman’sintroductiontowhatitmeanstosigndigitally.

http://www.androidbook.com/item/3279:OurresearchonunderstandingAndroidpackages.Youwillseehowtosign.apkfiles,furtherlinkstohowtosharedatabetweenpackages,moreonshareduserIDs,andinstructionstoinstallanduninstallpackages.

http://www.androidbook.com/item/3908:OurresearchnotesonallaspectsofAndroidlibrarysupport,includingolderscreenshots,newerscreenshots,usefulURLs,samplecode,and

more.

http://android-developers.blogspot.com/2011/10/changes-to-library-projects-in-android.html:WhathaschangedinlibrariesatthetimeofAndroid4.0andthereasonsforthechange?Thisblogalsotalksaboutfuturedirectionsforworkingwithlibraries.

http://tools.android.com/tips/non-constant-fields:Insightfuldiscussionoftheroleofnon-finalvariablesandhowtheyaffectswitchstatements.

http://tools.android.com/knownissues:AndroiddocumentationofknownissuesintheSDKToolsandtheADTreleases.AlsonotethedomainnameofthisURL;thissiteisdedicatedtoallaspectsofAndroidtooling.

http://docs.oracle.com/javase/7/docs/technotes/tools/windows/keytool.htmlExcellentdocumentationonkeytool,jarsigner,andthesigningprocessitself.

http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforafilecalledProAndroid5_Ch13_TestAndroidLibraries.zip.ThisZIPfilecontainstwoprojects:onealibraryandtheotherthatusesthislibrary.AlsotakealookataprojectcalledProAndroid5_Ch13_TestHandlers.zip,whichcontainsthecodetoworkwithhandlersincludingworkerthreads.

SummaryThischaptergaveyouaquickrun-downonhowpackages,processes,components,andthreadsinteractinanAndroidapplication.Thischapterhasalsodocumentedthelibrarysupportforsharingassetsbetweenmultipleapplications.Thischapteralsohasintroducedhandlers,akeyconceptintheAndroidSDK.Inthenextchapter,wewillgivedetailedcoverageofAsyncTask,whichcombinestheworkerthreadsandhandlersintoasimplerprogrammingabstractiontouse.

Chapter14

BuildingandConsumingServicesTheAndroidplatformprovidesacompletesoftwarestack.Thismeansyougetanoperatingsystemandmiddleware,aswellasworkingapplications(suchasaphonedialer).Alongsideallofthis,youhaveanSDKthatyoucanusetowriteapplicationsfortheplatform.Thusfar,we’veseenthatwecanbuildapplicationsthatdirectlyinteractwiththeuserthroughauserinterface.Wehavenot,however,discussedbackgroundservicesorthepossibilitiesofbuildingcomponentsthatruninthebackground.

Inthischapter,wearegoingtofocusonbuildingandconsumingservicesinAndroid.Firstwe’lldiscussconsumingHTTPservices,andthenwe’llcoveranicewaytodosimplebackgroundtasks,andfinallywe’lldiscussinterprocesscommunication—thatis,communicationbetweenapplicationsonthesamedevice.

ConsumingHTTPServicesAndroidapplicationsandmobileapplicationsingeneralaresmallappswithalotoffunctionality.Oneofthewaysthatmobileappsdeliversuchrichfunctionalityonsuchasmalldeviceisthattheypullinformationfromvarioussources.Forexample,mostAndroidsmartphonescomewiththeMapsapplication,whichprovidessophisticatedmappingfunctionality.We,however,knowthattheapplicationisintegratedwiththeGoogleMapsAPIandotherservices,whichprovidemostofthesophistication.

Thatsaid,itislikelythattheapplicationsyouwritewillalsoleverageinformationfromotherapplicationsandAPIs.AcommonintegrationstrategyistouseHTTP.Forexample,youmighthaveaJavaservletavailableontheInternetthatprovidesservicesyouwanttoleveragefromoneofyourAndroidapplications.HowdoyoudothatwithAndroid?Interestingly,theAndroidSDKshipswithavariationofApache’sHttpClient(http://hc.apache.org/httpcomponents-client-ga/),whichisuniversallyused.TheAndroidversionhasbeenmodifiedforAndroid,buttheAPIsareverysimilartotheAPIsintheApacheversion.

TheApacheHttpClientisacomprehensiveHTTPclient.ItoffersfullsupportfortheHTTPprotocol.Inthissection,wewilldiscussusingtheHttpClienttomakeHTTPGETandHTTPPOSTcalls.IfyouareworkingwithRESTfulservices,youwouldprobablyusetheotherHTTPoperationsaswell(PUT,DELETE,etc.).

UsingtheHttpClientforHTTPGETRequestsHere’soneofthegeneralpatternsforusingtheHttpClient:

1. CreateanHttpClient(orgetanexistingreference).

2. InstantiateanewHTTPmethod,suchasPostMethodorGetMethod.

3. SetHTTPparameternames/values.

4. ExecutetheHTTPcallusingtheHttpClient.

5. ProcesstheHTTPresponse.

Listing14-1showshowtoexecuteanHTTPGETusingtheHttpClient.

NoteWegiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.Also,becausethecodeattemptstousetheInternet,youwillneedtoaddandroid.permission.INTERNETtoyourmanifestfilewhenmakingHTTPcallsusingHttpClient.

Alsonotethatinthefollowingexamples,allwebservicescallsshouldbeputintobackgroundthreadssoasnottoblockthemainUIthread.Seelaterinthischapter,aswellasChapter15,foranexcellentdeep-diveonhowtodothat.Forthepurposesofthischapter,thosedetailsareexcludedtohelpwithunderstandingservices.

Listing14-1.UsingHttpClientandHttpGet:HttpGetDemo.java

publicclassHttpGetDemoextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

BufferedReaderin=null;try{

HttpClientclient=newDefaultHttpClient();HttpGetrequest=newHttpGet("http://code.google.com/android/");HttpResponseresponse=client.execute(request);

in=newBufferedReader(newInputStreamReader(response.getEntity().getContent()));

StringBuffersb=newStringBuffer("");Stringline="";StringNL=System.getProperty("line.separator");while((line=in.readLine())!=null){sb.append(line+NL);

}in.close();

Stringpage=sb.toString();System.out.println(page);}catch(Exceptione){e.printStackTrace();}finally{if(in!=null){try{in.close();}catch(IOExceptione){e.printStackTrace();}}}}}

TheHttpClientisabletoconsumethevariousHTTPrequesttypes,suchasHttpGet,HttpPost,andsoon.Listing14-1usestheHttpClienttogetthecontentsofthehttp://code.google.com/android/URL.TheactualHTTPrequestisexecutedwiththecalltoclient.execute().Afterexecutingtherequest,thecodereadstheentireresponseintoastringobject.NotethattheBufferedReaderisclosedinthefinallyblock,whichalsoclosestheunderlyingHTTPconnection.

ForourexampleweembeddedtheHTTPlogicinsideofanactivity,butwedon’tneedtobewithinthecontextofanactivitytouseHttpClient.YoucanuseitfromwithinthecontextofanyAndroidcomponentoruseitaspartofastand-aloneclass.Infact,youshouldn’tuseHttpClientdirectlywithinanactivity,becauseawebcallcouldtakeawhiletocompleteandcauseanApplicationNotResponding(ANR)pop-up.We’llcoverthattopiclaterinthischapter.Fornowwe’regoingtocheatalittlesowecanfocusonhowtomakeHttpClientcalls.

ThecodeinListing14-1executesanHTTPrequestwithoutpassinganyHTTPparameterstotheserver.Youcanpassname/valueparametersaspartoftherequestbyappendingname/valuepairstotheURL,asshowninListing14-2.

Listing14-2.AddingParameterstoanHTTPGETRequest

HttpGetrequest=newHttpGet("http://somehost/Upload.aspx?one=value1&two=value2");client.execute(request);

WhenyouexecuteanHTTPGET,theparameters(namesandvalues)oftherequestarepassedaspartoftheURL.Passingparametersthiswayhassomelimitations.Namely,thelengthofaURLshouldbekeptbelow2,048characters.Ifyouhavemorethanthisamount

ofdatatosubmit,youshoulduseHTTPPOSTinstead.ThePOSTmethodismoreflexibleandpassesparametersaspartoftherequestbody.

UsingtheHttpClientforHTTPPOSTRequests(aMultipartExample)MakinganHTTPPOSTcallisverysimilartomakinganHTTPGETcall(seeListing14-3).ThisexampleiscalledSimpleHTTPPost.

Listing14-3.MakinganHTTPPOSTRequestwiththeHttpClient

HttpClientclient=newDefaultHttpClient();HttpPostrequest=newHttpPost("http://www.androidbook.com/akc/display");List<NameValuePair>postParameters=newArrayList<NameValuePair>();postParameters.add(newBasicNameValuePair("url","DisplayNoteIMPURL"));postParameters.add(newBasicNameValuePair("reportId","4788"));postParameters.add(newBasicNameValuePair("ownerUserId","android"));postParameters.add(newBasicNameValuePair("aspire_output_format","embedded-xml"));UrlEncodedFormEntityformEntity=newUrlEncodedFormEntity(postParameters);request.setEntity(formEntity);HttpResponseresponse=client.execute(request);

ThecodeinListing14-3wouldreplacethethreelinesinListing14-1wheretheHttpGetisused.Everythingelsecouldstaythesame.TomakeanHTTPPOSTcallwiththeHttpClient,youhavetocalltheexecute()methodoftheHttpClientwithaninstanceofHttpPost.WhenmakingHTTPPOSTcalls,yougenerallypassURL-encodedname/valueformparametersaspartoftheHTTPrequest.TodothiswiththeHttpClient,youhavetocreatealistthatcontainsinstancesofNameValuePairobjectsandthenwrapthatlistwithaUrlEncodedFormEntityobject.TheNameValuePairwrapsaname/valuecombination,andtheUrlEncodedFormEntityclassknowshowtoencodealistofNameValuePairobjectssuitableforHTTPcalls(generallyPOSTcalls).AfteryoucreateaUrlEncodedFormEntity,youcansettheentitytypeoftheHttpPosttotheUrlEncodedFormEntityandthenexecutetherequest.

InListing14-3,wecreatedanHttpClientandtheninstantiatedtheHttpPostwiththeURLoftheHTTPendpoint.Next,wecreatedalistofNameValuePairobjectsandpopulateditwithseveralname/valueparameters.WethencreatedaUrlEncodedFormEntityinstance,passingthelistofNameValuePairobjectsto

itsconstructor.Finally,wecalledthesetEntity()methodofthePOSTrequestandthenexecutedtherequestusingtheHttpClientinstance.

HTTPPOSTisactuallymuchmorepowerfulthanthis.WithanHTTPPOST,wecanpasssimplename/valueparameters,asshowninListing14-3,aswellascomplexparameterssuchasfiles.HTTPPOSTsupportsanotherrequest-bodyformatknownasamultipartPOST.WiththistypeofPOST,youcansendname/valueparametersasbefore,alongwitharbitraryfiles.Unfortunately,theversionofHttpClientshippedwithAndroiddoesnotdirectlysupportmultipartPOST.Toachievethisgoalinthepast,we’verecommendedthatyougrabthreeotherlibraries:ApacheCommonsIO,Mime4j,andHttpMime.

NowwerecommendthatyoudownloadtheIonlibrary,whichhastwodependencies.Allthreejarfilescanbefoundatthesetwosites:

https://github.com/koush/ion#jars(ionandandroidasync)

https://code.google.com/p/google-gson/downloads/list(gson)

Listing14-4demonstratesamultipartPOSTusingAndroid.ThisexampleiscalledMultipartHTTPPost.

Listing14-4.MakingaMultipartPOSTCall

publicclassTestMultipartPostextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

try{Ion.with(this,"http://www.androidbook.com/akc/update/PublicUploadTest").setMultipartParameter("field1","Thisisfieldnumber1").setMultipartParameter("field2","Field2isshorter").setMultipartFile("datafile",newFile(Environment.getExternalStorageDirectory()+"/testfile.txt")).asString().setCallback(newFutureCallback<String>(){@OverridepublicvoidonCompleted(Exceptione,Stringresult){System.out.println(result);}});

}catch(Exceptione){//DosomethingaboutexceptionsSystem.out.println("Gotexception:"+e);}}}

NoteThemultipartexampleusesseveral.jarfilesthatarenotincludedaspartoftheAndroidruntime.Toensurethatthe.jarfileswillbepackagedaspartofyour.apkfile,youneedtoaddthemasexternal.jarfilesinEclipse.Todothis,right-clickyourprojectinEclipse,selectProperties,chooseJavaBuildPath,selecttheLibrariestab,andthenselectAddExternalJARs.

Followingthesestepswillmakethe.jarfilesavailableduringcompiletimeaswellasruntime.

ToexecuteamultipartPOSTusingtheIonlibrary,yousimplyputtogethertheappropriatecallstobuildaURL,addparameters,definethereturntype,andsetupacallbackmethod.ThiswillrunasynchronouslyandthecallbackwillbeinvokedontheUIthreadoncearesponseisreceivedfromthewebserver.Intheexample,theresultstringiswrittentoLogCat.YourapplicationisprobablygoingtoreceivebackaJsonObjectwhichthecallbackwouldthenprocess.ButrealizethattheresponsefromthewebserverhasalreadybeenconvertedintoaJsonObjectforyou,makingtheprocessinginthecallbackthatmucheasier.Listing14-4addsthreepartstotherequest:twostringpartsandatextfile.Torunthisexampleyourselfyouwillneedtoputatestfile.txtfileontotheexternalstorageareaofyourdeviceoremulator.

Finally,ifyouarebuildinganapplicationthatrequiresyoutopassamultipartPOSTtoawebresource,you’lllikelyhavetodebugthesolutionusingadummyimplementationoftheserviceonyourlocalworkstation.Whenyou’rerunningapplicationsonyourlocalworkstation,normallyyoucanaccessthelocalmachinebyusinglocalhostorIPaddress127.0.0.1.WithAndroidapplications,however,youwillnotbeabletouselocalhost(or127.0.0.1)becausethedeviceoremulatorwillbeitsownlocalhost.Youdon’twanttopointthisclienttoaserviceontheAndroiddevice;youwanttopointtoyourworkstation.Torefertoyourdevelopmentworkstationfromtheapplicationrunninginthedeviceoremulator,you’llhavetouseyourworkstation’sIPaddressintheURL.

SOAP,JSON,andXMLParsersWhataboutSOAP?TherearelotsofSOAP-basedwebservicesontheInternet,buttodate,GooglehasnotprovideddirectsupportinAndroidforcallingSOAPwebservices.GoogleinsteadprefersREST-likewebservices,seeminglytoreducetheamountofcomputingrequiredontheclientdevice.However,thetradeoffisthatthedevelopermustdomoreworktosenddataandtoparsethereturneddata.Ideally,youwillhavesome

optionsforhowyoucaninteractwithyourwebservices.SomedevelopershaveusedthekSOAP2developerkittobuildSOAPclientsforAndroid.Wewon’tbecoveringthatapproach,butit’soutthereifyou’reinterested.

NoteTheoriginalkSOAP2sourceislocatedhere:http://ksoap2.sourceforge.net/.Theopensourcecommunityhas(thankfully!)contributedaversionofkSOAP2forAndroid,andyoucanfindoutmoreaboutithere:http://code.google.com/p/ksoap2-android/.

Oneapproachthat’sbeenusedsuccessfullyistoimplementyourownservicesontheInternet,whichcantalkSOAP(orwhatever)tothedestinationservice.ThenyourAndroidapplicationonlyneedstotalktoyourservices,andyounowhavecompletecontrol.Ifthedestinationserviceschange,youmightbeabletohandlethatwithouthavingtoupdateandreleaseanewversionofyourapplication.You’donlyhavetoupdatetheservicesonyourserver.Asidebenefitofthisapproachisthatyoucouldmoreeasilyimplementapaidsubscriptionmodelforyourapplication.Ifauserletstheirsubscriptionlapse,youcanturnthemoffatyourserver.

AndroiddoeshavesupportforJavaScriptObjectNotation(JSON).Thisisafairlycommonmethodofpackagingdatabetweenawebserverandaclient.TheJSONparsingclassesmakeitveryeasytounpackdatafromaresponsesoyourapplicationcanactonit.OrdigdeeperintotheGsonpackagereferencedearlierinthischapter.GsonisaJSONJavalibraryfromGoogle,anditsmainbenefitishoweasyitistoparseJSONinputintoJavaobjects,andviceversa.It’salsoveryfast.

AndroidalsohasacoupleofXMLparsersthatyoucanusetointerprettheresponsesfromtheHTTPcalls;therecommendedoneisXMLPullParser.

DealingwithExceptionsDealingwithexceptionsispartofanyprogram,butsoftwarethatmakesuseofexternalservices(suchasHTTPservices)mustpayadditionalattentiontoexceptionsbecausethepotentialforerrorsismagnified.ThereareseveraltypesofexceptionsthatyoucanexpectwhilemakinguseofHTTPservices.Thesearetransportexceptions,protocolexceptions,andtimeouts.Youshouldunderstandwhentheseexceptionscouldoccur.

Transportexceptionscanoccurduetoanumberofreasons,butthemostlikelyscenariowithamobiledeviceispoornetworkconnectivity.Protocolexceptions(e.g.,ClientProtocolException)areexceptionsattheHTTPprotocollayer.Theseincludeauthenticationerrors,invalidcookies,andsoon.Youcanexpecttoseeprotocolexceptionsif,forexample,youhavetosupplylogincredentialsaspartofyourHTTPrequestbutfailtodoso.Timeouts,withrespecttoHTTPcalls,comeintwoflavors:connectiontimeoutsandsockettimeouts.Aconnectiontimeout(e.g.,ConnectTimeoutException)canoccuriftheHttpClientisnotabletoconnecttotheHTTPserver—if,forexample,theserverisnotavailable.Asockettimeout(e.g.,SocketTimeoutException)canoccuriftheHttpClientfailstoreceivearesponse

withinadefinedtimeperiod.Inotherwords,theHttpClientwasabletoconnecttotheserver,buttheserverfailedtoreturnaresponsewithintheallocatedtimelimit.

Nowthatyouunderstandthetypesofexceptionsthatmightoccur,howdoyoudealwiththem?Fortunately,theHttpClientisarobustframeworkthattakesmostoftheburdenoffyourshoulders.Infact,theonlyexceptiontypesthatyou’llhavetoworryaboutaretheonesthatyou’llbeabletomanageeasily.TheHttpClienttakescareoftransportexceptionsbydetectingtransportissuesandretryingrequests(whichworksverywellwiththistypeofexception).Protocolexceptionsareexceptionsthatcangenerallybeflushedoutduringdevelopment.Timeoutsarethemostlikelyexceptionsthatyou’llhavetodealwith.Asimpleandeffectiveapproachtodealingwithbothtypesoftimeouts—connectiontimeoutsandsockettimeouts—istowraptheexecute()methodofyourHTTPrequestwithatry/catchandthenretryifafailureoccurs.

WhenusingtheHttpClientaspartofareal-worldapplication,youneedtopaysomeattentiontomultithreadingissuesthatmightcomeup.Let’sdelveintothesenow.

AddressingMultithreadingIssuesTheexampleswe’veshownsofarcreatedanewHttpClientforeachrequest.Inpractice,however,youcouldcreateoneHttpClientfortheentireapplicationandusethatforallofyourHTTPcommunication.It’spossibletoassociateaconnectionpoolwiththisHttpClient,whichyou’llnowsee.WithoneHttpClientservicingallofyourHTTPrequests,youshouldpayattentiontomultithreadingissuesthatcouldsurfaceifyoumakesimultaneousrequeststhroughthesameHttpClient.Fortunately,theHttpClientprovidesfacilitiesthatmakethiseasy—allyouhavetodoiscreatetheDefaultHttpClientusingaThreadSafeClientConnManager,asshowninListing14-5.ThisexampleprojectisHttpSingleton.

Listing14-5.CreatinganHttpClientforMultithreading:CustomHttpClient.java

publicclassCustomHttpClient{privatestaticHttpClientcustomHttpClient;

/**AprivateConstructorpreventsinstantiation*/privateCustomHttpClient(){}

publicstaticsynchronizedHttpClientgetHttpClient(){if(customHttpClient==null){HttpParamsparams=newBasicHttpParams();HttpProtocolParams.setVersion(params,HttpVersion.HTTP_1_1);HttpProtocolParams.setContentCharset(params,HTTP.DEFAULT_CONTENT_CHARSET);HttpProtocolParams.setUseExpectContinue(params,

true);HttpProtocolParams.setUserAgent(params,System.getProperty("http.agent")//Couldalsohaveusedthefollowingwhichisbrowser-orientedasopposedto//device-oriented://newWebView(getApplicationContext()).getSettings().getUserAgentString());

ConnManagerParams.setTimeout(params,1000);

HttpConnectionParams.setConnectionTimeout(params,5000);HttpConnectionParams.setSoTimeout(params,10000);

SchemeRegistryschReg=newSchemeRegistry();schReg.register(newScheme("http",PlainSocketFactory.getSocketFactory(),80));schReg.register(newScheme("https",SSLSocketFactory.getSocketFactory(),443));ClientConnectionManagerconMgr=newThreadSafeClientConnManager(params,schReg);

customHttpClient=newDefaultHttpClient(conMgr,params);}returncustomHttpClient;}

publicObjectclone()throwsCloneNotSupportedException{thrownewCloneNotSupportedException();}}

IfyourapplicationneedstomakemorethanafewHTTPcalls,youshouldcreateanHttpClientthatservicesallyourHTTPrequests.Thesimplestwaytodothisistocreateasingletonclassthatcanbeaccessedfromanywhereintheapplication,aswe’veshownhere.ThisisafairlystandardJavapatterninwhichwesynchronizeaccesstoagettermethod,andthatgettermethodreturnstheoneandonlyHttpClientobjectforthesingleton,creatingitthefirsttimeasnecessary.

Now,takealookatthegetHttpClient()methodofCustomHttpClient.ThismethodisresponsibleforcreatingoursingletonHttpClient.Wesetsomebasicparameters,sometimeoutvalues,andtheschemesthatourHttpClientwillsupport

(thatis,HTTPandHTTPS).NoticethatwhenweinstantiatetheDefaultHttpClient(),wepassinaClientConnectionManager.TheClientConnectionManagerisresponsibleformanagingHTTPconnectionsfortheHttpClient.BecausewewanttouseasingleHttpClientforalltheHTTPrequests(requeststhatcouldoverlapifwe’reusingthreads),wecreateaThreadSafeClientConnManager.

WealsoshowyouasimplerwayofcollectingtheresponsefromtheHTTPrequest,usingaBasicResponseHandler.ThecodeforouractivitythatusesourCustomHttpClientisinListing14-6.

Listing14-6.UsingOurCustomHttpClient:HttpActivity.java

publicclassHttpActivityextendsActivity{privateHttpClienthttpClient;@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

httpClient=CustomHttpClient.getHttpClient();getHttpContent();}

publicvoidgetHttpContent(){try{HttpGetrequest=newHttpGet("http://www.google.com/");Stringpage=httpClient.execute(request,newBasicResponseHandler());System.out.println(page);}catch(IOExceptione){//covers://ClientProtocolException//ConnectTimeoutException//ConnectionPoolTimeoutException//SocketTimeoutExceptione.printStackTrace();}}}

Forthissampleapplication,wedoasimpleHTTPgetoftheGooglehomepage.WealsouseaBasicResponseHandlerobjecttotakecareofrenderingthepageasabig

String,whichwethenwriteouttoLogCat.Asyoucansee,addingaBasicResponseHandlertotheexecute()methodisveryeasytodo.

YoumaybetemptedtotakeadvantageofthefactthateachAndroidapplicationhasanassociatedApplicationobject.Bydefault,ifyoudon’tdefineacustomapplicationobject,Androidusesandroid.app.Application.Here’stheinterestingthingabouttheapplicationobject:therewillalwaysbeexactlyoneapplicationobjectforyourapplication,andallofyourcomponentscanaccessit(usingtheglobalcontextobject).ItispossibletoextendtheApplicationclassandaddfunctionalitysuchasourCustomHttpClient.However,inourcasethereisreallynoreasontodothiswithintheApplicationclassitself,andyouwillbemuchbetteroffnotmessingwiththeApplicationclasswhenyoucansimplycreateaseparatesingletonclasstohandlethistypeofneed.

FunwithTimeoutsThereareotherterrificadvantagestosettingupasingleHttpClientforourapplicationtouse.Wecanmodifythepropertiesofitinoneplace,andeveryonecantakeadvantageofit.Forexample,ifwewanttosetupcommontimeoutvaluesforourHTTPcalls,wecandothatwhencreatingourHttpClientbycallingtheappropriatesetterfunctionsagainstourHttpParamsobject.PleaserefertoListing14-5andthegetHttpClient()method.Noticethattherearethreetimeoutswecanplaywith.Thefirstisatimeoutfortheconnectionmanager,anditdefineshowlongweshouldwaittogetaconnectionoutoftheconnectionpoolmanagedbytheconnectionmanager.Inourexample,wesetthisto1second.Abouttheonlytimewemighteverhavetowaitisifallconnectionsfromthepoolareinuse.Thesecondtimeoutvaluedefineshowlongweshouldwaittomakeaconnectionoverthenetworktotheserverontheotherend.Here,weusedavalueof2seconds.Andlastly,wesetasockettimeoutvalueto4secondstodefinehowlongweshouldwaittogetdatabackforourrequest.

Correspondingtothethreetimeoutsdescribedpreviously,wecouldgetthesethreeexceptions:ConnectionPoolTimeoutException,ConnectTimeoutException,orSocketTimeoutException.AllthreeoftheseexceptionsaresubclassesofIOException,whichwe’veusedinourHttpActivityinsteadofcatchingeachsubclassexceptionseparately.

Ifyouinvestigateeachoftheparameter-settingclassesthatweusedingetHttpClient(),youmightdiscoverevenmoreparametersthatyouwouldfinduseful.

We’vedescribedforyouhowtosetupanHttpClientwithapoolofconnectionsforuseacrossyourapplication.Andtheimplicationisthateverytimeyouneedtouseaconnection,thevarioussettingswillapplytoyourparticularneeds.Butwhatifyouwantdifferentsettingsforaparticularmessage?Thankfully,there’saneasywaytodothataswell.WeshowedyouhowtouseanHttpGetoranHttpPostobjecttodescribetherequesttobemadeacrossthenetwork.InasimilarwaytohowwesetHttpParamsonourHttpClient,youcansetHttpParamsonbothHttpGetandHttpPost

objects.ThesettingsyouapplyatthemessagelevelwilloverridethesettingsattheHttpClientlevelwithoutchangingtheHttpClientsettings.Listing14-7showswhatthismightlooklikeifwewantedtohaveasockettimeoutof1minuteinsteadof4secondsforoneparticularrequest.YouwouldusetheselinesinplaceofthelinesinthetryblockofgetHttpContent()inListing14-6.

Listing14-7.OverridingtheSocketTimeoutattheRequestLevel

HttpGetrequest=newHttpGet("http://www.google.com/");HttpParamsparams=request.getParams();HttpConnectionParams.setSoTimeout(params,60000);//1minuterequest.setParams(params);Stringpage=httpClient.execute(request,newBasicResponseHandler());System.out.println(page);

UsingtheHttpURLConnectionAndroidprovidesanotherwaytodealwithHTTPservices,andthatisusingthejava.net.HttpURLConnectionclass.ThisisnotunliketheHttpClientclasseswe’vejustcovered,butHttpURLConnectiontendstorequiremorestatementstogetthingsdone.HttpURLConnectionisalsonotthreadsafe.Ontheotherhand,thisclassismuchsmallerandlightweightthanHttpClient,soyoucansimplycreatetheonesyouneed.StartingwiththeGingerbreadrelease,itisalsofairlystable,soyoushouldconsideritforappsonmorerecentdeviceswhenyoujustneedbasicHTTPfeaturesandyouwantacompactapplication.

UsingtheAndroidHttpClientAndroid2.2introducedanewsubclassofHttpClientcalledAndroidHttpClient.TheideabehindthisclassistomakethingseasierforthedeveloperofAndroidappsbyprovidingdefaultvaluesandlogicappropriateforAndroidapps.Forexample,thetimeoutvaluesfortheconnectionandthesocket(thatis,operation)defaultto20secondseach.TheconnectionmanagerdefaultstotheThreadSafeClientConnManager.Forthemostpart,itisinterchangeablewiththeHttpClientweusedinthepreviousexamples.Thereareafewdifferences,though,thatyoushouldbeawareof:

TocreateanAndroidHttpClient,youinvokethestaticnewInstance()methodoftheAndroidHttpClientclass,likethis:

AndroidHttpClienthttpClient=AndroidHttpClient.newInstance("my-http-agent-string");

NoticethattheparametertothenewInstance()methodisanHTTPagentstring.Youmostlikelydon’twanttohardcodethis,soyouhavetwooptionsasfollows,whichunfortunatelycanreturndifferentstrings.Thesecondoneisprobablytheoneyouwanttouseasitlooksmorelikewhatabrowserwouldsend(atleastinourexperiments).

//Thefirstoptionisadevice-levelagentstringStringhttpAgent=System.getProperty("http.agent");//Thissecondoptionlookslikeabrowser’sagentstringhttpAgent=newWebView(context).getSettings().getUserAgentString();

Ofcourse,youarealsofreetobuildyourownagentstringusinganythingavailabletoyourapp;it’stheserverthat’sgoingtoparseittobetterunderstandthedevice,andifyoucontroltheserver,youcanusewhatevervaluesyousentfromtheapp.

Whenexecute()iscalledonthisclient,youmustbeinathreadseparatefromthemainUIthread.Thismeansthatyou’llgetanexceptionifyousimplyattempttoreplaceourpreviousHttpClientwithanAndroidHttpClient.ItisbadpracticetomakeHTTPcallsfromthemainUIthread,soAndroidHttpClientwon’tletyou.We’llbecoveringthreadingissuesinthenextsection.

Youmustcallclose()ontheAndroidHttpClientinstancewhenyouaredonewithit.Thisissomemorycanbefreedupproperly.

Therearesomehandystaticmethodsfordealingwithcompressedresponsesfromaserver,including

modifyRequestToAcceptGzipResponse(HttpRequestrequest)

getCompressedEntity(byte[]data,ContentResolverresolver)

getUngzippedContent(HttpEntityentity)

Onceyou’veacquiredaninstanceoftheAndroidHttpClient,youcannotmodifyanyparametersettingsinit,norcanyouaddanyparametersettingstoit(suchastheHTTPprotocolversion,forexample).YouroptionsaretooverridesettingswithintheHttpGet

objectasshownpreviouslyortonotusetheAndroidHttpClient.

ThisconcludesourdiscussionofusingHTTPserviceswiththeHttpClient.ForagreattutorialonusingHttpClientandtheseotherconcepts,pleasecheckouttheApachesiteathttp://hc.apache.org/httpcomponents-client-ga/tutorial/html/.

We’veshownyouhowtooperatewithHTTP-basedservices.Butwhatifwewantedtorunsomebackgroundprocessingthatlastedlongerthanashortwhile,orwhatifwewantedtoinvokesomenon-UIfunctionalitythatexistsinanotherAndroidapplication?Fortheseneeds,Androidprovidesservices.Wewilldiscussthemnext.

UsingAndroidServicesAndroidsupportstheconceptofservices.Servicesarecomponentsthatruninthebackground,withoutauserinterface.YoucanthinkofthesecomponentsassimilartoWindowsservicesorUnixdaemons.Similartothesetypesofservices,Androidservicescanbealwaysavailablebutdon’thavetobeactivelydoingsomething.Moreimportant,Androidservicescanhavelifecyclesseparatefromactivities.Whenanactivitypauses,stops,orgetsdestroyed,theremaybesomeprocessingthatyouwanttocontinue.Servicesaregoodforthattoo.

Androidsupportstwotypesofservices:localservicesandremoteservices.Alocalserviceisaservicethatisonlyaccessibletotheapplicationthatishostingit,anditisnotaccessiblefromotherapplicationsrunningonthedevice.Generally,thesetypesofservicessimplysupporttheapplicationthatishostingtheservice.Aremoteserviceisaccessiblefromotherapplicationsonthedeviceinadditiontotheapplicationhostingtheservice.RemoteservicesdefinethemselvestoclientsusingAndroidInterfaceDefinitionLanguage(AIDL).We’regoingtotalkaboutbothofthesetypesofservices,althoughinthenextfewchapters,we’regoingdeepintolocalservices.Therefore,wewillintroducethemherebutnotspendthatmuchtimeonthem.We’llcoverremoteservicesinmoredetailinthischapter.

UnderstandingServicesinAndroidTheAndroidServiceclassisawrapperofsortsforcodethathasservice-likebehavior.However,aServiceobjectdoesnotcreateitsownthreadsautomatically.ForaServiceobjecttousethreads,thedevelopermustmakeithappen.Thismeansthatwithoutaddingthreadingtoaservice,thecodeoftheservicewillrunonthemainthread.Ifourserviceisperformingoperationsthatwillcompletequickly,thiswon’tbeaproblem.Ifourservicemightrunforawhile,wedefinitelywanttoinvolvethreading.KeepinmindthereisnothingwrongwithusingAsyncTaskstodothreadingwithinservices.

Androidsupportstheconceptofaservicefortworeasons:

First,toallowyoutoimplementbackgroundtaskseasily.

Second,toallowyoutodointerprocesscommunicationbetweenapplicationsrunningonthesamedevice.

ThesetworeasonscorrespondtothetwotypesofservicesthatAndroidsupports:localservicesandremoteservices.Anexampleofthefirstcasemightbealocalserviceimplementedaspartofane-mailapplication.Theservicecouldhandlethesendingofanewe-mailtothee-mailserver,completewithattachmentsandretries.Asthiscouldtakeawhiletocomplete,aserviceisanicewayofwrappingupthatfunctionalitysothemainthreadcankickitoffandgetbacktotheuser.Plus,ifthee-mailactivitygoesaway,youstillwantthesente-mailstobedelivered.Anexampleofthesecondcase,aswe’llseelater,isalanguagetranslationapplication.Supposeyouhaveseveralapplicationsrunningonadevice,andyouneedaservicetoaccepttextthatneedstobetranslatedfromonelanguagetoanother.Ratherthanrepeatthelogicineveryapplication,youcouldwritearemotetranslationserviceandhavetheapplicationstalktotheservice.

AlocalservicegetsinitializedeitherbyaclientbindingtoitusingbindService(),orbyaclientstartingitusingstartService().RemoteservicesaretypicallyalwaysinitializedwithbindService().Aboundservicegetsinstantiatedwhenthefirstclientbindstoit,anddestroyedwhenthelastclientunbindsfromit.Asclientscomeinandoutoftheforeground,theycanbindandunbindasneeded,toensurethattheserviceisn’trunningunnecessarily.Thishelpstopreservebatterylife.However,itisunwisetobindinonResume()andunbindinonPause()becausethatcouldcausealotofunnecessarystartingandstoppingoftheservice.It’sbettertobindandunbindinonCreate()andonDestroy(),orinonStart()andonStop().BindingisonlyallowedfromanApplicationContext,anActivity,anotherService,oraContentProvider.ThatmeansnotfromFragmentsandnotfromBroadcastReceivers.

WhenaserviceisinsteadstartedwithstartService(),itwillremainrunninguntilitisstopped,eitherbyaclientorbytellingitselftostop.Foralocalservicethatwantstoperformworkinthebackground,considerinstantiatingitwithstartService()soitcanremainrunningeveniftheactivitythatstarteditgoesaway.TechnicallyaBroadcastReceivercanstartaserviceusingstartService(),sincetheservicecanthencontinuetoexistoncetheshort-livedBroadcastReceiverterminates.Ifyoudocreateaservicethatwillruninthebackgroundevenwhentheactivitieshavegoneaway,youmaywanttoimplementonBind()forwhentheuserwantstoregaincontroloftheservice.Anewactivitycouldbindtotheexistingserviceandthencallservicemethodsonit.

Thereareexamplesoflocalservicesthatdonotcreatebackgroundthreads,butthismaynotbeveryusefulinpractice.Aservicedoesnotinherentlycreateanythreads,socodeofaservicewillbydefaultrunonthemainUIthread.Theremaynotbeanyrealadvantagetowrappingthiscodeinaservicethen,sinceyoucouldjustcallmethodsofaclasstoexecutethatlogic.Itismorecommonforalocalservicetohaveitsownthreadsofexecution,whichcanbestartedeitherwhenthefirstclientbindstoit,orbecauseofastartService()command.

Now,wecanbeginadetailedexaminationofthetwotypesofservices.Wewillstartbytalkingaboutlocalservicesandthendiscussremoteservices.Asmentionedbefore,localservicesareservicesthatarecalledonlybytheapplicationthathoststhem.Remote

servicesareservicesthatsupportaremoteprocedurecall(RPC)mechanism.Theseservicesallowexternalclients,onthesamedevice,toconnecttotheserviceanduseitsfacilities.Therearetwomainwaysofcallingremoteservices:usinganAIDLinterfaceandusingaMessenger.Bothwillbecovered.

NoteThesecondtypeofserviceinAndroidisknownbyseveralnames:remoteservice,AIDL-supportingservice,AIDLservice,externalservice,andRPCservice.Thesetermsallrefertothesametypeofservice—onethat’smeanttobeaccessedremotelybyotherapplicationsrunningonthedevice.

UnderstandingLocalServicesLocalservicesareservicesthataregenerallystartedviaContext.startService().Oncestarted,thesetypesofserviceswillcontinuetorununtilaclientcallsContext.stopService()ontheserviceortheserviceitselfcallsstopSelf().NotethatwhenContext.startService()iscalledandtheservicehasnotalreadybeencreated,thesystemwillinstantiatetheserviceandcalltheservice’sonStartCommand()method.KeepinmindthatcallingContext.startService()aftertheservicehasbeenstarted(thatis,whileitexists)willnotresultinanotherinstanceoftheservice,butwillreinvoketherunningservice’sonStartCommand()method.Hereareacoupleofexamplesoflocalservices:

Aservicetomonitorsensordatafromthedeviceanddoanalysis,issuingalertsifacertainconditionisrealized.Thisservicemightrunconstantly.

Atask-executorservicethatletsyourapplication’sactivitiessubmitjobsandqueuethemforprocessing.Thisservicemightonlyrunforthedurationoftheoperationtosubmitthejob.

Listing14-8demonstratesalocalservicebyimplementingaservicethatexecutesbackgroundtasks.We’llendupwithfourartifactsrequiredtocreateandconsumetheservice:BackgroundService.java(theserviceitself),main.xml(alayoutfilefortheactivity),MainActivity.java(anactivityclasstocalltheservice),andAndroidManifest.xml.Listing14-8onlycontainsBackgroundService.java.We’lldissectthiscodefirstandthenmoveontotheotherthree.

Listing14-8.ImplementingaLocalService:BackgroundService.java

publicclassBackgroundServiceextendsService{privatestaticfinalStringTAG="BackgroundService";privateNotificationManagernotificationMgr;privateThreadGroupmyThreads=newThreadGroup("ServiceWorker");

@Override

publicvoidonCreate(){super.onCreate();

Log.v(TAG,"inonCreate()");notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);displayNotificationMessage("BackgroundServiceisrunning");}

@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){super.onStartCommand(intent,flags,startId);

intcounter=intent.getExtras().getInt("counter");Log.v(TAG,"inonStartCommand(),counter="+counter+",startId="+startId);

newThread(myThreads,newServiceWorker(counter),"BackgroundService").start();

returnSTART_STICKY;}

classServiceWorkerimplementsRunnable{privateintcounter=-1;publicServiceWorker(intcounter){this.counter=counter;}

publicvoidrun(){finalStringTAG2="ServiceWorker:"+Thread.currentThread().getId();//dobackgroundprocessinghere…we'lljustsleep…try{Log.v(TAG2,"sleepingfor10seconds.counter="+counter);Thread.sleep(10000);Log.v(TAG2,"...wakingup");}catch(InterruptedExceptione){

Log.v(TAG2,"...sleepinterrupted");}}}

@OverridepublicvoidonDestroy(){Log.v(TAG,"inonDestroy().Interruptingthreadsandcancellingnotifications");myThreads.interrupt();notificationMgr.cancelAll();super.onDestroy();}

@OverridepublicIBinderonBind(Intentintent){Log.v(TAG,"inonBind()");returnnull;}

privatevoiddisplayNotificationMessage(Stringmessage){

PendingIntentcontentIntent=PendingIntent.getActivity(this,0,newIntent(this,MainActivity.class),0);

Notificationnotification=newNotificationCompat.Builder(this).setContentTitle(message).setContentText("Touchtoturnoffservice").setSmallIcon(R.drawable.emo_im_winking).setTicker("Startingup!!!")//.setLargeIcon(aBitmap).setContentIntent(contentIntent).setOngoing(true).build();

notificationMgr.notify(0,notification);}}

ThestructureofaServiceobjectissomewhatsimilartoanactivity.ThereisanonCreate()methodwhereyoucandoinitialization,andanonDestroy()whereyoudocleanup.Servicesdon’tpauseorresumethewayactivitiesdo,sowedon’tuseonPause()oronResume()methods.Inthisexample,wewon’tbebindingtothe

localservice,butbecauseServicerequiresanimplementationoftheonBind()method,weprovideonethatsimplyreturnsnull.It’sworthmentioningthatyoucouldhavealocalservicethatimplementsonBind()anddoesnotuseonStartCommand().

GoingbacktoouronCreate()method,wedon’tneedtodomuchexcepttonotifytheuserthatthisservicehasbeencreated.WedothisusingtheNotificationManager.You’veprobablynoticedthenotificationbaratthetopleftofanAndroidscreen.Bypullingdownonthis,theusercanviewmessagesofimportance,andbytouchingnotificationscanactonthenotifications,whichusuallymeansreturningtosomeactivityrelatedtothenotification.Withservices,becausetheycanberunning,oratleastexisting,inthebackgroundwithoutavisibleactivity,therehastobesomewayfortheusertogetbackintouchwiththeservice,perhapstoturnitoff.Therefore,wecreateaNotificationobject,populateitwithaPendingIntent,whichwillgetusbacktoourcontrolactivity,andpostit.ThisallhappensinthedisplayNotificationMessage()method.NotethatourNotificationobjectneedstoexistaslongasourserviceexists,soweusesetOngoing(true)tokeepitinthenotificationslistuntilweclearitourselvesfromourservice’sonDestroy()method.ThemethodweusedinonDestroy()toclearournotificationiscancelAll()ontheNotificationManager.

There’sanotherthingyouneedtohaveforthisexampletowork.You’llneedtocreateadrawablenamedemo_im_winkingandplaceitwithinyourproject’sdrawablefolder.AgoodsourceofdrawablesforthisdemonstrationpurposeistolookundertheAndroidplatformfolderatAndroidSDK/platforms/<version>/data/res/drawable,where<version>istheversionyou’reinterestedin.Unfortunately,youcan’treliablyrefertoAndroidsystemdrawablesfromyourcode,soyou’llneedtocopywhatyouwantovertoyourproject’sdrawablesfolder.Ifyouchooseadifferentdrawablefileforyourexample,justgoaheadandrenametheresourceIDintheconstructorfortheNotification.

WhenanintentissentintoourserviceusingstartService(),onCreate()iscalledifnecessary,andouronStartCommand()methodiscalledtoreceivethecaller’sintent.Inourcase,we’renotgoingtodoanythingspecialwithit,excepttounpackthecounteranduseittostartabackgroundthread.Inareal-worldservice,wewouldexpectanydatatobepassedtousviatheintent,andthiscouldincludeURIs,forexample.NoticetheuseofaThreadGroupwhencreatingtheThread.Thiswillprovetobeusefullaterwhenwewanttogetridofourbackgroundthreads.AlsonoticethestartIdparameter.ThisissetforusbyAndroidandisauniqueidentifieroftheservicecallssincethisservicewasstarted.

OurServiceWorkerclassisatypicalrunnableandiswheretheworkhappensforourservice.Inourparticularcase,we’resimplyloggingsomemessagesandsleeping.We’realsocatchinganyinterruptionsandloggingthem.Onethingwe’renotdoingismanipulatingtheuserinterface.We’renotupdatinganyviewsforexample.Becausewe’renotonthemainthreadanymore,wecannottouchtheUIdirectly.TherearewaysforourServiceWorkertoeffectchangesintheuserinterface,andwe’llgetintothosedetailsinthenextfewchapters.

ThelastitemtopayattentiontoinourBackgroundServiceistheonDestroy()method.Thisiswhereweperformthecleanup.Forourexample,wewanttogetridofthethreadswecreatedearlier,ifanyarestillaround.Ifwedon’tdothis,theycouldsimplyhangaroundandtakeupmemory.Second,wewanttogetridofournotificationmessage.Becauseourserviceisgoingaway,there’snolongeranyneedfortheusertogettotheactivitytogetridofit.Inareal-worldapplication,however,wemightwanttokeepourworkersworking.Ifourserviceissendinge-mails,wecertainlydon’twanttosimplykilloffthethreads.Ourexampleisoverlysimple,becauseweimplythroughtheuseoftheinterrupt()methodthatyoucaneasilykilloffbackgroundthreads.Inreality,however,themostyoucandoisinterrupt.Thiswon’tnecessarilykilloffathread,though.Therearedeprecatedmethodsforkillingthreads,butyoushouldnotusethese.Theycancausememoryandstabilityproblemsforyouandyourusers.Interruptingworksinourexample,becausewe’redoingsleeps,whichcanbeinterrupted.

It’sworthwhiletakingalookattheThreadGroupclassbecauseitprovideswaysforyoutogetaccesstoyourthreads.WecreatedasingleThreadGroupobjectwithinourserviceandthenusedthatwhencreatingourindividualthreads.WithinouronDestroy()methodoftheservice,wesimplyinterrupt()ontheThreadGroup,anditissuesaninterrupttoeachthreadintheThreadGroup.

Sothereyouhavethemakingsofasimplelocalservice.Beforeweshowyouthecodeforouractivity,Listing14-9showstheXMLlayoutfileforouruserinterface.

Listing14-9.ImplementingaLocalService:main.xml

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><Buttonandroid:id="@+id/startBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="StartService"android:onClick="doClick"/><Buttonandroid:id="@+id/stopBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="StopService"android:onClick="doClick"/></LinearLayout>

We’regoingtoshowtwobuttonsontheuserinterface,onetodostartService()andtheothertodostopService().WecouldhavechosentouseaToggleButton,butthenyouwouldnotbeabletocallstartService()multipletimesinarow.Thisisan

importantpoint.Thereisnotaone-to-onerelationshipbetweenstartService()andstopService().WhenstopService()iscalled,theserviceobjectwillbedestroyed,andallthreadscreatedfromallstartService()callsshouldalsogoaway.Now,let’slookatthecodeforouractivityinListing14-10.

Listing14-10.ImplementingaLocalService:MainActivity.java

publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="MainActivity";privateintcounter=1;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}

publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startBtn:Log.v(TAG,"Startingservice…counter="+counter);Intentintent=newIntent(MainActivity.this,BackgroundService.class);intent.putExtra("counter",counter++);startService(intent);break;caseR.id.stopBtn:stopService();}}

privatevoidstopService(){Log.v(TAG,"Stoppingservice…");if(stopService(newIntent(MainActivity.this,BackgroundService.class)))Log.v(TAG,"stopServicewassuccessful");elseLog.v(TAG,"stopServicewasunsuccessful");}

@OverridepublicvoidonDestroy(){stopService();

super.onDestroy();}}

OurMainActivitylooksalotlikeotheractivitiesyou’veseen.There’sasimpleonCreate()tosetupouruserinterfacefromthemain.xmllayoutfile.There’sadoClick()methodtohandlethebuttoncallbacks.Inourexample,we’recallingstartService()whentheStartServicebuttonispressed,andwe’recallingstopService()whentheStopServicebuttonispressed.Whenwestarttheservice,wewanttopassinsomedata,whichwedoviatheintent.WechosetopassthedataintheExtrasbundle,butwecouldhaveaddeditusingsetData()ifwehadaURI.Whenwestoptheservice,wechecktoseethereturnresult.Itshouldnormallybetrue,butiftheservicewasnotrunning,wecouldgetareturnoffalse.Last,whenouractivitydies,wewanttostoptheservice,sowealsostoptheserviceinouronDestroy()method.There’sonemoreitemtodiscuss,andthat’stheAndroidManifest.xmlfile,whichweshowinListing14-11.

Listing14-11.ImplementingaLocalService:AndroidManifest.xml

<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.simplelocal"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="8"/><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><activityandroid:name=".MainActivity"android:label="@string/app_name"android:launchMode="singleTop"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity><serviceandroid:name="BackgroundService"/></application>

</manifest>

Inadditiontoourregular<activity>tagsinthemanifestfile,wenowhavea<service>tag.Becausethisisalocalservicethatwe’recallingexplicitlyusingtheclassname,wedon’tneedtoputmuchintothe<service>tag.Allthatisrequiredisthenameofourservice.Butthereisoneotherthingtopointoutaboutthismanifestfile.Our

servicecreatesanotificationsothattheusercangetbacktoourMainActivityif,forexample,theuserpressedtheHomekeyonMainActivitywithoutstoppingtheservice.

TheMainActivityisstillthere;it’sjustnotvisible.OnewaytogetbacktotheMainActivityistoclickthenotificationthatourservicecreated.ThenotificationmanagerdeliversourintentbacktoourapplicationandwouldnormallycauseanewinstanceofMainActivitytohandlethenewintent.Topreventthisfromhappening,wesetanattributeinourmanifestfileforMainActivitycalledandroid:launchMode,andwesetittosingleTop.ThiswillhelpensurethattheexistinginvisibleMainActivitywillbebroughtforwardanddisplayed,ratherthancreatinganotherMainActivity.

Whenyourunthisapplication,youwillseeourtwobuttons.ByclickingtheStartServicebutton,youwillbeinstantiatingtheserviceandcallingonStartCommand().OurcodelogsseveralmessagestoLogCat,soyoucanfollowalong.GoaheadandclickStartServiceseveraltimesinarow,evenquickly.Youwillseethreadscreatedtohandleeachrequest.You’llalsonoticethatthevalueofcounterispassedalongthroughtoeachServiceWorkerthread.WhenyoupresstheStopServicebutton,ourservicewillgoaway,andyou’llseethelogmessagesfromourMainActivity’sstopService()method,fromourBackgroundService’sonDestroy()method,andpossiblyfromServiceWorkerthreadsiftheygotinterrupted.

Youshouldalsonoticethenotificationmessagewhentheservicehasbeenstarted.Withtheservicerunning,goaheadandpresstheBackbuttonfromourMainActivityandnoticethatthenotificationmessagedisappears.Thismeansourservicehasgoneawayalso.TorestartourMainActivity,clickStartServicetogettheservicegoingagain.Now,presstheHomebutton.OurMainActivitydisappearsfromview,butthenotificationremains,meaningourserviceisstillinexistence.Goaheadandclickthenotification,andyou’llagainseeourMainActivity.

Notethatourexampleusesanactivitytointerfacewiththeservice,butanycomponentinyourapplicationcanusetheservice.Thisincludesotherservices,activities,genericclasses,andsoon.Alsonotethatourservicedoesnotstopitself;itreliesontheactivitytodothatforit.Therearesomemethodsavailabletoaservicetoallowtheservicetostopitself,namelystopSelf()andstopSelfResult().Obviously,ifwehavemultipleclientsforthisservice,wewouldn’twanttheservicetobestoppedbyoneiftheothersarestillusingit.Forastartedservicewithmultipleclients,itismorelikelyyou’dputlogicintheserviceitselftodecidewhentheservicecanorshouldbestopped,andtheservicewoulduseoneofthestop*()methodstodothat.

OurBackgroundServiceisatypicalexampleofaservicethatisusedbythecomponentsoftheapplicationthatishostingtheservice.Inotherwords,theapplicationthatisrunningtheserviceisalsotheonlyconsumer.Becausetheservicedoesnotsupportclientsfromoutsideitsprocess,theserviceisalocalservice.ThecriticalmethodsofalocalserviceareonCreate(),onStartCommand(),onBind(),stop*(),andonDestroy().

There’sanotheroptionwithalocalservice,andthatisforthecasewhereyou’llonlyhaveoneinstanceoftheservicewithonebackgroundthread.Inthiscase,intheonCreate()methodoftheBackgroundService,wecouldcreateathreadthatdoestheservice’sheavylifting.WecouldcreateandstartthethreadinonCreate()ratherthanonStartCommand().WecoulddothisbecauseonCreate()iscalledonlyonce,andwewantthethreadtobecreatedonlyonceduringthelifeoftheservice.Onethingwewouldn’thaveinonCreate(),though,isthecontentoftheintentpassedbystartService().Ifweneedthat,wemightaswellusethepatternasdescribedpreviously,andwe’djustknowthatonStartCommand()shouldbecalledonlyonce.

Androidhasyetanotherwaytoimplementalocalservicethatincludesabackgroundthreadautomatically:theIntentService.AsubclassofService,IntentServicereceivestheincomingIntentfromastartService()call,createsabackground(worker)threadforyou,andinvokesthecallbackonHandleIntent(Intentintent).Ifanotherintentisdeliveredtothisservicebeforetheworkerthreadhascompletedtheearlierintent,thenewintentwillsitandwaituntilthepreviousintenthasbeenprocessed,atwhichtimethenextintentinthequeuewillgetpassedtotheonHandleIntent()method.Whenallintentsfromtheinboundqueuehavecompletedprocessing,theservicewillstopitself(noneedforyoutodothat).

Thisconcludesourintroductiontolocalservices.Rememberthatwe’llgetintomoredetailsoflocalservicesinsubsequentchapters.Let’smoveontoAIDLservices—themorecomplicatedtypeofservice.

UnderstandingAIDLServicesIntheprevioussection,weshowedyouhowtowriteanAndroidservicethatisconsumedbytheapplicationthathoststheservice.Now,wearegoingtoshowyouhowtobuildaservicethatcanbeconsumedbyotherprocessesviaremoteprocedurecall(RPC).AswithmanyotherRPC-basedsolutions,inAndroidyouneedaninterfacedefinitionlanguage(IDL)todefinetheinterfacethatwillbeexposedtoclients.IntheAndroidworld,thisIDLiscalledAndroidInterfaceDefinitionLanguage(AIDL).Tobuildaremoteservice,youdothefollowing:

1. WriteanAIDLfilethatdefinesyourinterfacetoclients.TheAIDLfileusesJavasyntaxandhasan.aidlextension.UsethesamepackagenameinsideyourAIDLfileasthepackageforyourAndroidproject.

2. AddtheAIDLfiletoyourEclipseprojectunderthesrcdirectory.TheAndroidEclipseplug-inwillcalltheAIDLcompilertogenerateaJavainterfacefromtheAIDLfile(theAIDLcompileriscalledaspartofthebuildprocess).

3. Implementaservice,andreturntheinterfacefromtheonBind()method.

4. AddtheserviceconfigurationtoyourAndroidManifest.xml

file.Thesectionsthatfollowshowyouhowtoexecuteeachstep.

DefiningaServiceInterfaceinAIDLTodemonstrateanexampleofaremoteservice,wearegoingtowriteastock-quoterservice.Thisservicewillprovideamethodthattakesatickersymbolandreturnsthestockvalue.TowritearemoteserviceinAndroid,thefirststepistodefinetheserviceinterfacedefinitioninanAIDLfile.Listing14-12showstheAIDLdefinitionofIStockQuoteService.ThisfilegoesintothesameplaceasaregularJavafilewouldforyourStockQuoteServiceproject.

Listing14-12.TheAIDLDefinitionoftheStock-QuoterService

//ThisfileisIStockQuoteService.aidlpackagecom.androidbook.services.stockquoteservice;interfaceIStockQuoteService{doublegetQuote(Stringticker);}

TheIStockQuoteServiceacceptsthestock-tickersymbolasastringandreturnsthecurrentstockvalueasadouble.WhenyoucreatetheAIDLfile,theAndroidEclipseplug-inrunstheAIDLcompilertoprocessyourAIDLfile(aspartofthebuildprocess).IfyourAIDLfilecompilessuccessfully,thecompilergeneratesaJavainterfacesuitableforRPCcommunication.NotethatthegeneratedfilewillbeinthepackagenamedinyourAIDLfile—com.androidbook.services.stockquoteserviceinthiscase.

Listing14-13showsthegeneratedJavafileforourIStockQuoteServiceinterface.ThegeneratedfilewillbeputintothegenfolderofourEclipseproject.

Listing14-13.TheCompiler-GeneratedJavaFile

/**Thisfileisauto-generated.DONOTMODIFY.*Originalfile:C:\\android\\StockQuoteService\\src\\com\\androidbook\\services\\stockquoteservice\\IStockQuoteService.aidl*/packagecom.androidbook.services.stockquoteservice;importjava.lang.String;importandroid.os.RemoteException;importandroid.os.IBinder;importandroid.os.IInterface;importandroid.os.Binder;importandroid.os.Parcel;publicinterfaceIStockQuoteServiceextendsandroid.os.IInterface{

/**Local-sideIPCimplementationstubclass.*/publicstaticabstractclassStubextendsandroid.os.Binderimplementscom.androidbook.services.stockquoteservice.IStockQuoteService{privatestaticfinaljava.lang.StringDESCRIPTOR="com.androidbook.services.stockquoteservice.IStockQuoteService";/**Constructthestubatattachittotheinterface.*/publicStub(){this.attachInterface(this,DESCRIPTOR);}/***CastanIBinderobjectintoanIStockQuoteServiceinterface,*generatingaproxyifneeded.*/publicstaticcom.androidbook.services.stockquoteservice.IStockQuoteService

asInterface(android.os.IBinderobj){if((obj==null)){returnnull;}android.os.IInterfaceiin=(android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);if(((iin!=null)&&(iininstanceofcom.androidbook.services.stockquoteservice.IStockQuoteService))){return((com.androidbook.services.stockquoteservice.IStockQuoteService)iin);}returnnewcom.androidbook.services.stockquoteservice.IStockQuoteService.Stub.Proxy(obj);}publicandroid.os.IBinderasBinder(){returnthis;}@OverridepublicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags)throwsandroid.os.RemoteException{switch(code){

caseINTERFACE_TRANSACTION:{reply.writeString(DESCRIPTOR);returntrue;}caseTRANSACTION_getQuote:{data.enforceInterface(DESCRIPTOR);java.lang.String_arg0;_arg0=data.readString();double_result=this.getQuote(_arg0);reply.writeNoException();reply.writeDouble(_result);returntrue;}}returnsuper.onTransact(code,data,reply,flags);}privatestaticclassProxyimplementscom.androidbook.services.stockquoteservice.IStockQuoteService{privateandroid.os.IBindermRemote;Proxy(android.os.IBinderremote){mRemote=remote;}publicandroid.os.IBinderasBinder(){returnmRemote;}publicjava.lang.StringgetInterfaceDescriptor(){returnDESCRIPTOR;}publicdoublegetQuote(java.lang.Stringticker)throwsandroid.os.RemoteException{android.os.Parcel_data=android.os.Parcel.obtain();android.os.Parcel_reply=android.os.Parcel.obtain();double_result;try{_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(ticker);mRemote.transact(Stub.TRANSACTION_getQuote,_data,_reply,0);_reply.readException();

_result=_reply.readDouble();}finally{_reply.recycle();_data.recycle();}return_result;}}staticfinalintTRANSACTION_getQuote=(IBinder.FIRST_CALL_TRANSACTION+0);}publicdoublegetQuote(java.lang.Stringticker)throwsandroid.os.RemoteException;}

Notethefollowingimportantpointsregardingthegeneratedclasses:

TheinterfacewedefinedintheAIDLfileisimplementedasaninterfaceinthegeneratedcode(thatis,thereisaninterfacenamedIStockQuoteService).

AstaticabstractclassnamedStubextendsandroid.os.BinderandimplementsIStockQuoteService.Notethattheclassisanabstractclass.

AninnerclassnamedProxyimplementstheIStockQuoteServicethatproxiestheStubclass.

TheAIDLfilemustresideinthepackagewherethegeneratedfilesaresupposedtobe(asspecifiedintheAIDLfile’spackagedeclaration).

Now,let’smoveonandimplementtheAIDLinterfaceinaserviceclass.

ImplementinganAIDLInterfaceIntheprevioussection,wedefinedanAIDLfileforastock-quoterserviceandgeneratedthebindingfile.Now,wearegoingtoprovideanimplementationofthatservice.Toimplementtheservice’sinterface,weneedtowriteaclassthatextendsandroid.app.ServiceandimplementstheIStockQuoteServiceinterface.Theclasswearegoingtowritewe’llcallStockQuoteService.Toexposetheservicetoclients,ourStockQuoteServicewillneedtoprovideanimplementationoftheonBind()method,andwe’llneedtoaddsomeconfigurationinformationtotheAndroidManifest.xmlfile.Listing14-14showsanimplementationoftheIStockQuoteServiceinterface.ThisfilealsogoesintothesrcfolderoftheStockQuoteServiceproject.

Listing14-14.TheIStockQuoteServiceServiceImplementation

publicclassStockQuoteServiceextendsService{privatestaticfinalStringTAG="StockQuoteService";publicclassStockQuoteServiceImplextendsIStockQuoteService.Stub{@OverridepublicdoublegetQuote(Stringticker)throwsRemoteException{Log.v(TAG,"getQuote()calledfor"+ticker);return20.0;}}

@OverridepublicvoidonCreate(){super.onCreate();Log.v(TAG,"onCreate()called");}

@OverridepublicvoidonDestroy(){super.onDestroy();Log.v(TAG,"onDestroy()called");}

@OverridepublicIBinderonBind(Intentintent){Log.v(TAG,"onBind()called");returnnewStockQuoteServiceImpl();}}

TheStockQuoteService.javaclassinListing14-14resemblesthelocalBackgroundServicewecreatedearlier,butwithouttheNotificationManager.TheimportantdifferenceisthatwenowimplementtheonBind()method.RecallthattheStubclassgeneratedfromtheAIDLfilewasanabstractclassandthatitimplementedtheIStockQuoteServiceinterface.Inourimplementationoftheservice,wehaveaninnerclassthatextendstheStubclasscalledStockQuoteServiceImpl.Thisclassservesastheremote-serviceimplementation,andaninstanceofthisclassisreturnedfromtheonBind()method.Withthat,wehaveafunctionalAIDLservice,althoughexternalclientscannotconnecttoityet.

Toexposetheservicetoclients,weneedtoaddaservicedeclarationintheAndroidManifest.xmlfile,andthistime,weneedanintentfiltertoexposetheservice.Listing14-15showstheservicedeclarationfortheStockQuoteService.The<service>tagisachildofthe<application>tag.

Listing14-15.ManifestDeclarationfortheIStockQuoteService

<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.stockquoteservice"android:versionCode="1"android:versionName="1.0"><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><serviceandroid:name="StockQuoteService"><intent-filter><actionandroid:name="com.androidbook.services.stockquoteservice.IStockQuoteService"/></intent-filter></service></application><uses-sdkandroid:minSdkVersion="4"/></manifest>

Aswithallservices,wedefinetheservicewewanttoexposewitha<service>tag.ForanAIDLservice,wealsoneedtoaddan<intent-filter>withan<action>entryfortheserviceinterfacewewanttoexpose.

Withthisinplace,wehaveeverythingweneedtodeploytheservice.WhenyouarereadytodeploytheserviceapplicationfromEclipse,justgoaheadandchooseRunAsthewayyouwouldforanyotherapplication.EclipsewillcommentintheConsolethatthisapplicationhasnoLauncher,butitwilldeploytheappanyway,whichiswhatwewant.Let’snowlookathowwewouldcalltheservicefromanotherapplication(onthesamedevice,ofcourse).

CallingtheServicefromaClientApplicationWhenaclienttalkstoaservice,theremustbeaprotocolorcontractbetweenthetwo.WithAndroid,thecontractisinourAIDLfile.Sothefirststepinconsumingaserviceistotaketheservice’sAIDLfileandcopyittoyourclientproject.WhenyoucopytheAIDLfiletotheclientproject,theAIDLcompilercreatesthesameinterface-definitionfilethatwascreatedwhentheservicewasimplemented(intheservice-implementationproject).Thisexposestotheclientallofthemethods,parameters,andreturntypesontheservice.Let’screateanewprojectandcopytheAIDLfile:

1. CreateanewAndroidprojectnamedStockQuoteClient.Useadifferentpackagename,suchascom.androidbook.stockquoteclient.UseMainActivityfortheCreateActivityfield.

2. CreateanewJavapackageinthisprojectnamedcom.androidbook.services.stockquoteserviceinthesrcdirectory.

3. CopytheIStockQuoteService.aidlfilefromtheStockQuoteServiceprojecttothisnewpackage.Notethatafteryoucopythefiletotheproject,theAIDLcompilerwillgeneratetheassociatedJavafile.

Theserviceinterfacethatyouregenerateservesasthecontractbetweentheclientandtheservice.ThenextstepistogetareferencetotheservicesowecancallthegetQuote()method.Withremoteservices,wehavetocallthebindService()methodratherthanthestartService()method.Listing14-16showsanactivityclassthatactsasaclientoftheIStockQuoteServiceservice.Listing14-17containsthelayoutfilefortheactivity.

Listing14-16showsourMainActivity.javafile.Realizethatthepackagenameoftheclientactivityisnotthatimportant—youcanputtheactivityinanypackageyou’dlike.However,theAIDLartifactsthatyoucreatearepackage-sensitivebecausetheAIDLcompilergeneratescodefromthecontentsoftheAIDLfile.

Listing14-16.AClientoftheIStockQuoteServiceService

publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="StockQuoteClient";privateIStockQuoteServicestockService=null;privateToggleButtonbindBtn;privateButtoncallBtn;

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

bindBtn=(ToggleButton)findViewById(R.id.bindBtn);callBtn=(Button)findViewById(R.id.callBtn);}

publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.bindBtn:if(((ToggleButton)view).isChecked()){

bindService(newIntent(IStockQuoteService.class.getName()),serConn,Context.BIND_AUTO_CREATE);}else{unbindService(serConn);callBtn.setEnabled(false);}break;caseR.id.callBtn:callService();break;}}

privatevoidcallService(){try{doubleval=stockService.getQuote("ANDROID");Toast.makeText(MainActivity.this,"Valuefromserviceis"+val,Toast.LENGTH_SHORT).show();}catch(RemoteExceptionee){Log.e("MainActivity",ee.getMessage(),ee);}}

privateServiceConnectionserConn=newServiceConnection(){

@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){Log.v(TAG,"onServiceConnected()called");stockService=IStockQuoteService.Stub.asInterface(service);bindBtn.setChecked(true);callBtn.setEnabled(true);}

@OverridepublicvoidonServiceDisconnected(ComponentNamename){Log.v(TAG,"onServiceDisconnected()called");bindBtn.setChecked(false);callBtn.setEnabled(false);stockService=null;

}};

protectedvoidonDestroy(){Log.v(TAG,"onDestroy()called");if(callBtn.isEnabled())unbindService(serConn);super.onDestroy();}}

TheactivitydisplaysourlayoutandgrabsareferencetotheCallServicebuttonsowecanproperlyenableitwhentheserviceisrunninganddisableitwhentheserviceisstopped.WhentheuserclickstheBindbutton,theactivitycallsthebindService()method.Similarly,whentheuserclicksUnBind,theactivitycallstheunbindService()method.NoticethatthreeparametersarepassedtothebindService()method:anIntentwiththenameoftheAIDLservice,aServiceConnectioninstance,andaflagtoautocreatetheservice.

Listing14-17.TheIStockQuoteServiceServiceClientLayout

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<ToggleButtonandroid:id="@+id/bindBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textOff="Bind"android:textOn="Unbind"android:onClick="doClick"/>

<Buttonandroid:id="@+id/callBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="CallService"android:enabled="false"android:onClick="doClick"/></LinearLayout>

Withaboundservice,suchasanAIDLservice,youneedtoprovideanimplementationoftheServiceConnectioninterface.Thisinterfacedefinestwomethods:onecalledbythesystemwhenaconnectiontotheservicehasbeenestablishedandonecalledwhentheconnectiontotheservicehasbeendestroyed.Inouractivityimplementation,wedefinetheServiceConnectionfortheIStockQuoteService.Whenwecallthe

bindService()method,wepassinthereferencetothis(i.e.,serConn).Whentheconnectiontotheserviceisestablished,theonServiceConnected()callbackisinvoked,andwethenobtainareferencetotheIStockQuoteServiceusingtheStubandenabletheCallServicebutton.

NotethatthebindService()callisanasynchronouscall.Itisasynchronousbecausetheprocessorservicemightnotberunningandthusmighthavetobecreatedorstarted.Andwecannotwaitonthemainthreadfortheservicetostart.BecausebindService()isasynchronous,theplatformprovidestheServiceConnectioncallback,soweknowwhentheservicehasbeenstartedandwhentheserviceisnolongeravailable.TheseServiceConnectioncallbackswillrunonthemainthreadthough,sothey’llhaveaccesstotheUIcomponentsifnecessary.

PleasenoticetheonServiceDisconnected()callback.Thisdoesnotgetinvokedwhenweunbindfromtheservice.ItisinvokediftheservicecrashesorifAndroiddecidestokilltheservice,forexampleifmemoryisgettinglow.Ifthiscallbackfires,weshouldnotthinkthatwe’restillconnected,andwemightneedtoreinvokethebindService()call.ThatiswhywechangethestatusofourbuttonsintheUIwhenthiscallbackisinvoked.Butnoticewesaid“wemightneedtoreinvokethebindService()call.”AndroidcouldrestartourserviceforusandinvokeouronServiceConnected()callback.Youcantrythisyourselfbyrunningtheclient,bindingtotheservice,andusingDDMStodoaStopontheStockQuoteServiceapplication.

Whenyourunthisexample,watchthelogmessagesinLogCattogetafeelforwhatisgoingonbehindthescenes.

Inourserviceexamplesthusfar,wehavestrictlydealtwithpassingsimpleJavaprimitivetypes.Androidservicesactuallysupportpassingcomplextypes,too.Thisisveryuseful,especiallyforAIDLservices,becauseyoumighthaveanopen-endednumberofparametersthatyouwanttopasstoaservice,andit’sunreasonabletopassthemallassimpleprimitives.Itmakesmoresensetopackagethemascomplextypesandthenpassthemtotheservice.

Let’sseehowwecanpasscomplextypestoservices.

PassingComplexTypestoServicesPassingcomplextypestoandfromservicesrequiresmoreworkthanpassingJavaprimitivetypes.Beforeembarkingonthiswork,youshouldgetanideaofAIDL’ssupportfornonprimitivetypes:

AIDLsupportsStringandCharSequence.

AIDLallowsyoutopassotherAIDLinterfaces,butyouneedtohaveanimportstatementforeachAIDLinterfaceyoureference(evenifthereferencedAIDLinterfaceisinthesamepackage).

AIDLallowsyoutopasscomplextypesthatimplementtheandroid.os.Parcelableinterface.Youneedtohavean

importstatementinyourAIDLfileforthesetypes.

AIDLsupportsjava.util.Listandjava.util.Map,withafewrestrictions.TheallowabledatatypesfortheitemsinthecollectionincludeJavaprimitive,String,CharSequence,andandroid.os.Parcelable.YoudonotneedimportstatementsforListorMap,butyoudoneedthemfortheParcelables.

Nonprimitivetypes,otherthanString,requireadirectionalindicator.Directionalindicatorsincludein,out,andinout.inmeansthevalueissetbytheclient,outmeansthevalueissetbytheservice,andinoutmeansboththeclientandservicesetthevalue.Androidavoidsserializingthevaluesifthey’renotflowingintheindicateddirection,whichhelpsoverallperformance.

TheParcelableinterfacetellstheAndroidruntimehowtoserializeanddeserializeobjectsduringthemarshallingandunmarshallingprocess.Listing14-18showsaPersonclassthatimplementstheParcelableinterface.

Listing14-18.ImplementingtheParcelableInterface

//ThisfileisPerson.javapackagecom.androidbook.services.stock2;importandroid.os.Parcel;importandroid.os.Parcelable;

publicclassPersonimplementsParcelable{privateintage;privateStringname;publicstaticfinalParcelable.Creator<Person>CREATOR=newParcelable.Creator<Person>(){publicPersoncreateFromParcel(Parcelin){returnnewPerson(in);}

publicPerson[]newArray(intsize){returnnewPerson[size];}};

publicPerson(){}

privatePerson(Parcelin){readFromParcel(in);}

@OverridepublicintdescribeContents(){return0;}

@OverridepublicvoidwriteToParcel(Parcelout,intflags){out.writeInt(age);out.writeString(name);}

publicvoidreadFromParcel(Parcelin){age=in.readInt();name=in.readString();}

publicintgetAge(){returnage;}

publicvoidsetAge(intage){this.age=age;}

publicStringgetName(){returnname;}

publicvoidsetName(Stringname){this.name=name;}}

Togetstartedonimplementingthis,createanewAndroidProjectinEclipsecalledStockQuoteService2.ForCreateActivity,useanameofMainActivity,anduseapackageofcom.androidbook.services.stock2.ThenaddthePerson.javafilefromListing14-18tothecom.androidbook.services.stock2packageofournewproject.

TheParcelableinterfacedefinesthecontractforthemarshallingandunmarshallingofobjects.UnderlyingtheParcelableinterfaceistheParcelcontainerobject.TheParcelclassisafastserialization/deserializationmechanismspeciallydesignedforinterprocesscommunicationwithinAndroid.Theclassprovidesmethodsthatyouusetoflattenyourmemberstothecontainerandtoexpandthemembersbackfromthecontainer.Toproperlyimplementanobjectforinterprocesscommunication,wehavetodothefollowing:

1. ImplementtheParcelableinterface.ThismeansthatyouimplementwriteToParcel()andreadFromParcel().Thewritemethodwillwritetheobjecttotheparcel,andthereadmethodwillreadtheobjectfromtheparcel.Notethattheorderinwhichyouwritepropertiesmustbethesameastheorderinwhichyoureadthem.

2. AddastaticfinalpropertytotheclasswiththenameCREATOR.Thepropertyneedstoimplementtheandroid.os.Parcelable.Creator<T>interface.

3. ProvideaconstructorfortheParcelablethatknowshowtocreatetheobjectfromtheParcel.

4. DefineaParcelableclassinan.aidlfilethatmatchesthe.javafilecontainingthecomplextype.TheAIDLcompilerwilllookforthisfilewhencompilingyourAIDLfiles.AnexampleofaPerson.aidlfileisshowninListing14-19.ThisfileshouldbeinthesameplaceasPerson.java.

NoteSeeingParcelablemighthavetriggeredthequestion,whyisAndroidnotusingthebuilt-inJavaserializationmechanism?ItturnsoutthattheAndroidteamcametotheconclusionthattheserializationinJavaisfartooslowtosatisfyAndroid’sinterprocess-communicationrequirements.SotheteambuilttheParcelablesolution.TheParcelableapproachrequiresthatyouexplicitlyserializethemembersofyourclass,butintheend,yougetamuchfasterserializationofyourobjects.

AlsorealizethatAndroidprovidestwomechanismsthatallowyoutopassdatatoanotherprocess.Thefirstistopassabundletoanactivityusinganintent,andthesecondistopassaParcelabletoaservice.Thesetwomechanismsarenotinterchangeableandshouldnotbeconfused.Thatis,theParcelableisnotmeanttobepassedtoanactivity.Ifyouwanttostartanactivityandpassitsomedata,useaBundle.ParcelableismeanttobeusedonlyaspartofanAIDLdefinition.

Listing14-19.AnExampleofaPerson.aidlFile

//ThisfileisPerson.aidlpackagecom.androidbook.services.stock2;parcelablePerson;

Youwillneedan.aidlfileforeachParcelableinyourproject.Inthiscase,wehavejustoneParcelable,whichisPerson.Youmaynoticethatyoudon’tgetaPerson.javafilecreatedinthegenfolder.Thisistobeexpected.Wealreadyhavethisfilefromwhenwecreateditpreviously.

Now,let’susethePersonclassinaremoteservice.Tokeepthingssimple,wewill

modifyourIStockQuoteServicetotakeaninputparameteroftypePerson.TheideaisthatclientswillpassaPersontotheservicetotelltheservicewhoisrequestingthequote.ThenewIStockQuoteService.aidllookslikeListing14-20.

Listing14-20.PassingParcelablestoServices

//ThisfileisIStockQuoteService.aidlpackagecom.androidbook.services.stock2;importcom.androidbook.services.stock2.Person;

interfaceIStockQuoteService{StringgetQuote(inStringticker,inPersonrequester);}

ThegetQuote()methodnowacceptstwoparameters:thestock’stickersymbolandaPersonobjecttospecifywhoismakingtherequest.NotethatwehavedirectionalindicatorsontheparametersbecausetheparametersincludenonprimitivetypesandthatwehaveanimportstatementforthePersonclass.ThePersonclassisalsointhesamepackageastheservicedefinition(com.androidbook.services.stock2).

TheserviceimplementationnowlookslikeListing14-21,withtheMainActivitylayoutinListing14-22.

Listing14-21.TheStockQuoteService2Implementation

packagecom.androidbook.services.stock2;//ThisfileisStockQuoteService2.java

importandroid.app.Notification;importandroid.app.NotificationManager;importandroid.app.PendingIntent;importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;importandroid.os.RemoteException;

publicclassStockQuoteService2extendsService{privateNotificationManagernotificationMgr;

publicclassStockQuoteServiceImplextendsIStockQuoteService.Stub{publicStringgetQuote(Stringticker,Personrequester)throwsRemoteException{return"Hello"+requester.getName()+"!Quotefor"+ticker+"is20.0";

}}

@OverridepublicvoidonCreate(){super.onCreate();

notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

displayNotificationMessage("onCreate()calledinStockQuoteService2");}

@OverridepublicvoidonDestroy(){displayNotificationMessage("onDestroy()calledinStockQuoteService2");//ClearallnotificationsfromthisservicenotificationMgr.cancelAll();super.onDestroy();}

@OverridepublicIBinderonBind(Intentintent){displayNotificationMessage("onBind()calledinStockQuoteService2");returnnewStockQuoteServiceImpl();}

privatevoiddisplayNotificationMessage(Stringmessage){PendingIntentcontentIntent=PendingIntent.getActivity(this,0,newIntent(this,MainActivity.class),0);

Notificationnotification=newNotificationCompat.Builder(this).setContentTitle("StockQuoteService2").setContentText(message).setSmallIcon(R.drawable.emo_im_happy).setTicker(message)//.setLargeIcon(aBitmap).setContentIntent(contentIntent).setOngoing(true)

.build();

notificationMgr.notify(R.id.app_notification_id,notification);}}

Listing14-22.TheStockQuoteService2Layout

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Thisiswheretheservicecouldaskforhelp."/></LinearLayout>

Thedifferencesbetweenthisimplementationandthepreviousonearethatwebroughtbackthenotifications,andwenowreturnthestockvalueasastringandnotadouble.ThestringreturnedtotheusercontainsthenameoftherequesterfromthePersonobject,whichdemonstratesthatwereadthevaluesentfromtheclientandthatthePersonobjectwaspassedcorrectlytotheservice.

Thereareafewotherthingsthatneedtobedonetomakethiswork:

1. Findtheemo_im_happy.pngimagefilefromunderAndroidSDK/platforms/android-19/data/res/drawable-mdpi,andcopyittothe/res/drawabledirectoryofourproject.Orchangethenameoftheresourceinthecode,andputwhateverimageyouwantinthedrawablesfolder.

2. Addanew<itemtype=“id”name=“app_notification_id”/>tagtothe/res/values/strings.xmlfile.

3. WeneedtomodifytheapplicationintheAndroidManifest.xmlfileasshowninListing14-23.

Listing14-23.Modified<application>inAndroidManifest.xmlFileforStockQuoteService2

<?xmlversion="1.0"encoding="utf-8"?><manifest

xmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.stock2"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="8"/><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><activityandroid:name=".MainActivity"android:label="@string/app_name"android:launchMode="singleTop"><intent-filter><actionandroid:name="android.intent.action.MAIN"/></intent-filter></activity><serviceandroid:name="StockQuoteService2"><intent-filter><actionandroid:name="com.androidbook.services.stock2.IStockQuoteService"/></intent-filter></service></application>

</manifest>

WhileitisOKtousethedotnotationforourandroid:name=”.MainActivity”attribute,itisnotOKtousedotnotationinsideofour<action>taginsidetheservice’s<intent-filter>tag.Weneedtospellitout;otherwise,ourclientwillnotfindtheservicespecification.

Last,we’llusethedefaultMainActivity.javafilethatsimplydisplaysabasiclayoutwithasimplemessage.Weshowedyouearlierhowtolaunchtotheactivityfromanotification.Thisactivitywouldservethatpurposealsoinreallife,butforthisexample,we’llkeepthatpartsimple.Nowthatwehaveourserviceimplementation,let’screateanewAndroidprojectcalledStockQuoteClient2.Usecom.daveforthepackageandMainActivityfortheactivityname.ToimplementaclientthatpassesthePersonobjecttotheservice,weneedtocopyeverythingthattheclientneedsfromtheserviceprojecttotheclientproject.Thereneedstobeanewsrcpackagecalledcom.androidbook.services.stock2toreceivethesecopiedfiles.Inourpreviousexample,allweneededwastheIStockQuoteService.aidlfile.WealsoneedtocopythePerson.javaandPerson.aidlfiles,becausethePersonobjectisnowpartoftheinterface.Afteryoucopythesethreefilestothecom.androidbook.services.stock2srcpackageoftheclientproject,modifymain.xmlaccordingtoListing14-24,andmodifyMainActivity.javaaccordingtoListing14-25.Orsimplyimportthisprojectfromthesourcecodeonourwebsite.

Listing14-24.Updatedmain.xmlforStockQuoteClient2

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">

<ToggleButtonandroid:id="@+id/bindBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textOff="Bind"android:textOn="Unbind"android:onClick="doClick"/><Buttonandroid:id="@+id/callBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="CallService"android:enabled="false"android:onClick="doClick"/></LinearLayout>

Listing14-25.CallingtheServicewithaParcelable

packagecom.dave;//ThisfileisMainActivity.javaimportcom.androidbook.services.stock2.IStockQuoteService;importcom.androidbook.services.stock2.Person;

publicclassMainActivityextendsActivity{

protectedstaticfinalStringTAG="StockQuoteClient2";privateIStockQuoteServicestockService=null;privateToggleButtonbindBtn;privateButtoncallBtn;

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

bindBtn=(ToggleButton)findViewById(R.id.bindBtn);callBtn=(Button)findViewById(R.id.callBtn);}

publicvoiddoClick(Viewview){switch(view.getId()){

caseR.id.bindBtn:if(((ToggleButton)view).isChecked()){bindService(newIntent(IStockQuoteService.class.getName()),serConn,Context.BIND_AUTO_CREATE);}else{unbindService(serConn);callBtn.setEnabled(false);}break;caseR.id.callBtn:callService();break;}}

privatevoidcallService(){try{Personperson=newPerson();person.setAge(47);person.setName("Dave");Stringresponse=stockService.getQuote("ANDROID",person);Toast.makeText(MainActivity.this,"Valuefromserviceis"+response,Toast.LENGTH_SHORT).show();}catch(RemoteExceptionee){Log.e("MainActivity",ee.getMessage(),ee);}}

privateServiceConnectionserConn=newServiceConnection(){

@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){Log.v(TAG,"onServiceConnected()called");stockService=IStockQuoteService.Stub.asInterface(service);bindBtn.setChecked(true);callBtn.setEnabled(true);}

@Override

publicvoidonServiceDisconnected(ComponentNamename){Log.v(TAG,"onServiceDisconnected()called");bindBtn.setChecked(false);callBtn.setEnabled(false);stockService=null;}};

protectedvoidonDestroy(){if(callBtn.isEnabled())unbindService(serConn);super.onDestroy();}}

Thisisnowreadytorun.Remembertosendovertheservicetothedeviceoremulatorbeforeyousendovertheclienttorun.TheuserinterfaceshouldlooklikeFigure14-1.

Figure14-1.UserinterfaceofStockQuoteClient2

Let’stakealookatwhatwe’vegot.Asbefore,webindtoourservice,andthenwecaninvokeaservicemethod.TheonServiceConnected()methodiswherewegettoldthatourserviceisrunning,sowecanthenenabletheCallServicebuttonsothebuttoncaninvokethecallService()method.Asshown,wecreateanewPersonobjectandsetitsAgeandNameproperties.Wethenexecutetheserviceanddisplaytheresultfromtheservicecall.TheresultlookslikeFigure14-2.

Figure14-2.ResultfromcallingtheservicewithaParcelable

Noticethatwhentheserviceiscalled,yougetanotificationinthestatusbar.Thisiscomingfromtheserviceitself.WebrieflytouchedonNotificationsearlierasawayforaservicetocommunicatetotheuser.Normally,servicesareinthebackgroundanddonotdisplayanysortofUI.Butwhatifaserviceneedstointeractwiththeuser?Whileit’stemptingtothinkthataservicecaninvokeanactivity,aserviceshouldneverinvokeanactivitydirectly.Aserviceshouldinsteadcreateanotification,andthenotificationshouldbehowtheusergetstothedesiredactivity.Thiswasshowninourlastexercise.We

definedasimplelayoutandactivityimplementationforourservice.Whenwecreatedthenotificationwithintheservice,wesettheactivityinthenotification.Theusercantouchthenotification,anditwilltaketheusertoouractivitythatispartofthisservice.Thiswillallowtheusertointeractwiththeservice.

Notificationsaresavedsothatyoucangettothembypullingdownfromthestatusbartoseethem.NotethefactthatwereusethesameIDforeverymessage.Thismeansthatweareupdatingtheoneandonlynotificationeverytime,ratherthancreatingnewnotificationentries.Therefore,ifyougototheNotificationsscreeninAndroidafterclickingBind,CallAgain,andUnbindafewtimes,youwillonlyseeonemessageinNotifications,anditwillbethelastonesentbyStockQuoteService2.IfweuseddifferentIDs,wecouldhavemultiplenotificationmessages,andwecouldupdateeachoneseparately.Notificationscanalsobesetwithadditionaluser“prompts”suchassound,lights,and/orvibration.

Itisalsousefultoseetheartifactsoftheserviceprojectandtheclientthatcallsit(seeFigure14-3).

Figure14-3.Theartifactsoftheserviceandtheclient

Figure14-3showstheEclipseprojectartifactsfortheservice(left)andtheclient(right).NotethatthecontractbetweentheclientandtheserviceconsistsoftheAIDLartifactsand

theParcelableobjectsexchangedbetweenthetwoparties.ThisisthereasonthatweseePerson.java,IStockQuoteService.aidl,andPerson.aidlonbothsides.BecausetheAIDLcompilergeneratestheJavainterface,stub,proxy,andsoonfromtheAIDLartifacts,thebuildprocesscreatestheIStockQuoteService.javafileontheclientsidewhenwecopythecontractartifactstotheclientproject.Nowyouknowhowtoexchangecomplextypesbetweenservicesandclients.Let’sbrieflytouchonanotherimportantaspectofcallingservices:synchronousversusasynchronousserviceinvocation.

Allofthecallsthatyoumakeonservicesaresynchronous.Thisbringsuptheobviousquestion:Doyouneedtoimplementallofyourservicecallsinaworkerthread?Notnecessarily.Onmostotherplatforms,it’scommonforaclienttouseaservicethatisacompleteblackbox,sotheclientwouldhavetotakeappropriateprecautionswhenmakingservicecalls.WithAndroid,youwilllikelyknowwhatisintheservice(generallybecauseyouwrotetheserviceyourself),soyoucanmakeaninformeddecision.Ifyouknowthatthemethodyouarecallingisdoingalotofheavylifting,youshouldconsiderusingasecondarythreadtomakethecall.Ifyouaresurethatthemethoddoesnothaveanybottlenecks,youcansafelymakethecallontheUIthread.Ifyouconcludethatit’sbesttomaketheservicecallwithinaworkerthread,youcancreatethethreadandthencalltheservice.YoucanthencommunicatetheresulttotheUIthread.

MessengersandHandlersThereisonemorewaytocommunicatewithaserviceinAndroid,andthatiswithMessengersandHandlers.ThismechanismisbuiltontopofAIDLservices,butwithoutyouhavingtoseeordealwithAIDL.LikeAIDLservices,youuseitwhentheserviceisinaseparateprocessfromtheclient.BoththeclientandtheservicewillimplementaMessengerandaHandler,andproceedtosendmessagesbackandforth.Youdon’tneedtospecifyany.aidlfiles;everythingiscodedinyourJavaclasses.Thisisafairlycommonwaytodointer-processservicecallsonAndroid,andissignificantlyeasierthanmessingwithAIDLyourself.

Here’saquickoverviewofhowitworks.Theclientbindstotheservice,andsetsupaMessengerandHandlertoreceiveresponsesfromtheservice.AcallbackwithintheHandlertakescareofmessagessentbackbytheservice.TheclientalsocreatesaMessengertosendmessagestotheservice.Ontheserviceside,there’sasimilarMessengerandHandlertoreceivetheincomingmessagesfromclients.AmessagecominginfromaclientincludesaMessengertouseforanyrepliestothatclient.Therefore,theservicecreatesonlyoneMessengerwhileaclientcreatestwo.Theclientsideisasynchronous,withtheserviceresponsecominglater.AproblemwiththeservicecallgeneratesaRemoteExceptionthattheclientcancaptureandactupon.

Let’sseeanexampleofhowthisworks.Thissampleapplicationhastwoparts:aMessengerClientandaMessengerService.Theywillrunasseparateprocessesonadevice.Theclientwilluseanon-UIfragmenttocontaintheserviceclientconnection.Thismeanstheclientactivitycangoawayandberecreatedduetoaconfigurationchange,andtheunderlyingserviceconnectionremainsinplace.Thisisapreferredwaytoconnect

toaservicefromanactivitysinceyoudon’twanttohavetoreconstructtheserviceclientconnectionjustbecausethedevicehasbeenrotatedforexample.Listing14-26showsthesignificantcodefromMessengerService.javatosetuptheHandlerandMessenger.Forafulllisting,refertotheMessengerServicesourceprojectforthischapter.

Listing14-26.Messenger/Handler-BasedServiceCode

publicclassMessengerServiceextendsService{NotificationManagermNM;ArrayList<Messenger>mClients=newArrayList<Messenger>();intmValue=0;publicstaticfinalintMSG_REGISTER_CLIENT=1;publicstaticfinalintMSG_UNREGISTER_CLIENT=2;publicstaticfinalintMSG_SET_SIMPLE_VALUE=3;publicstaticfinalintMSG_SET_COMPLEX_VALUE=4;publicstaticfinalStringTAG="MessengerService";/***Handlerofincomingmessagesfromclients.*/classIncomingHandlerextendsHandler{@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseMSG_REGISTER_CLIENT:mClients.add(msg.replyTo);Log.v(TAG,"Registeringclient");break;caseMSG_UNREGISTER_CLIENT:mClients.remove(msg.replyTo);Log.v(TAG,"Unregisteringclient");break;caseMSG_SET_SIMPLE_VALUE:mValue=msg.arg1;Log.v(TAG,"Receivingarg1:"+mValue);showNotification("Receivedarg1:"+mValue);for(inti=mClients.size()-1;i>=0;i--){try{mClients.get(i).send(Message.obtain(null,MSG_SET_SIMPLE_VALUE,mValue,0));}catch(RemoteExceptione){//Theclientisdead.Removeitfromthelist;//wearegoingthroughthelistfrombacktofront

//sothisissafetodoinsidetheloop.mClients.remove(i);}}break;caseMSG_SET_COMPLEX_VALUE:BundlemBundle=msg.getData();Log.v(TAG,"Receivingbundle:");if(mBundle!=null){showNotification("Gotcomplexmsg:myDouble="+mBundle.getDouble("myDouble"));for(Stringkey:mBundle.keySet()){Log.v(TAG,""+key);}}break;default:Log.v(TAG,"Gotsomeothermessage:"+msg.what);super.handleMessage(msg);}}}

//TargetforclientstosendmessagestoIncomingHandler.finalMessengermMessenger=newMessenger(newIncomingHandler());

@OverridepublicvoidonCreate(){mNM=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

//Displayanotificationaboutusstarting.Log.v(TAG,"Serviceisstarting");showNotification(getText(R.string.remote_service_started));}

@OverridepublicvoidonDestroy(){//Cancelthepersistentnotification.mNM.cancel(R.string.remote_service_started);

//Telltheuserwestopped.

Toast.makeText(this,R.string.remote_service_stopped,Toast.LENGTH_SHORT).show();}

/***Whenbindingtotheservice,wereturnaninterfacetoourmessenger*forsendingmessagestotheservice.*/@OverridepublicIBinderonBind(Intentintent){returnmMessenger.getBinder();}

/***Showanotificationwhilethisserviceisrunning.Notethat*wedon'tincludeanintentsincewe'rejustaservicehere.The*servicestopswhentheclienttellsitto.*/privatevoidshowNotification(CharSequencetext){Notificationnotification=newNotificationCompat.Builder(this).setContentTitle("MessengerService").setContentText(text).setSmallIcon(android.R.drawable.ic_dialog_info).setTicker(text).setOngoing(true).build();

mNM.notify(R.string.remote_service_started,notification);}

}

Inthissample,clientsregisterwiththeservice,unregisterwiththeservice,sendasimplemessage,orsendacomplexmessage.Whenaclientregisters,theserviceremembersitbysavingthepassed-inclientMessenger(i.e.,msg.replyTo)inmClients.Ifasimplemessageisreceived,theservicesendsacopyofthereceivedargumentvaluetoallknownclients.NoticehowrepliesaresenttoeachclientusingtheMessengersinmClientsthatcamefromeachclient.TheMessage’swhatfieldisjustaninttoindicatewhatserviceoperationisbeingcalled.Baseduponthewhatoperation,theservicewillextracttheappropriatearguments.SinceaMessageobjecthastwointargumentsavailable,thesimplecaseusesjustoneofthoseMessagefields.Whenmorecomplexdatamustbesent,aBundleobjectiscreated,populated,andattachedtotheMessagefortransmissiontothe

service.

Beawarethataservicehasa1MBbufferforpasseddata(inandout)forallin-processservicecalls,soyou’llwanttokeepMessagedatatoaminimum.Iftherearealotofsimultaneousservicecalls,youcouldexceedthebufferandgetaTransactionTooLargeException.

Ontheclientside,there’saMainActivityandaClientFrag(thenon-UIfragment).Forsimplicity’ssaketheactivityprovidestheUItotheuserwithoutusingaUIfragment.Listing14-27showstheMainActivity.Forafulllistingoftheproject,pleaseseetheMessengerClientprojectforthischapter.

Listing14-27.Messenger/Handler-BasedClientActivityCode

publicclassMainActivityextendsFragmentActivityimplementsISampleServiceClient{

protectedstaticfinalStringTAG="MessengerClient";privateTextViewmCallbackText;privateClientFragclientFrag;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

mCallbackText=(TextView)findViewById(R.id.callback);

//Getanon-UIfragmenttohandletheserviceinterface.//Ifouractivitygetsdestroyedandrecreated,thefragment//willstillbearoundandwejustneedtore-fetchit.if((clientFrag=(ClientFrag)getSupportFragmentManager().findFragmentByTag("clientFrag"))==null){updateStatus("CreatingaclientFrag.Noserviceyet.");clientFrag=ClientFrag.getInstance();getSupportFragmentManager().beginTransaction().add(clientFrag,"clientFrag").commit();}else{updateStatus("FoundexistingclientFrag,willuseit");}}

publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startBtn:clientFrag.doBindService();break;caseR.id.stopBtn:clientFrag.doUnbindService();break;caseR.id.simpleBtn:clientFrag.doSendSimple();break;caseR.id.complexBtn:clientFrag.doSendComplex();break;}}

@OverridepublicvoidupdateStatus(Stringstatus){mCallbackText.setText(status);}}

Noticehowthere’snomentionofaserviceinthisactivity,justaTextView,buttons,andaclientfragment.TheupdateStatus()methodisasaresultoftheISampleServiceClientinterfacethatthisactivityimplements,andallitneedstodoissetthetextintheUIaspassedin.Thebuttonssimplyinvokeamethodoftheclientfragment.Inarealapplication,therewouldbemorebusinessandUIlogicinthisactivityorinotherfragmentsthatareseparatefromtheservicecall.

Theclientfragmentiswherethefunis.Listing14-28showsthecodefromtheclientfragment.

Listing14-28.Messenger/Handler-BasedClientFragmentCode

publicclassClientFragextendsFragment{privatestaticfinalStringTAG="MessengerClientFrag";staticprivateClientFragmClientFrag=null;//applicationcontextwillbeusedtobindtotheservicebecause//fragmentscan'tbindandactivitiescangoaway.privateContextappContext=null;

//Messengerforsendingtoservice.MessengermService=null;//Flagindicatingwhetherwehavecalledbindontheservice.

booleanmIsBound;

//Instantiationmethodfortheclientfragment.Wejustwantone//andweusesetRetainInstance(true)soithangsaroundduring//configurationchanges.publicstaticClientFraggetInstance(){if(mClientFrag==null){mClientFrag=newClientFrag();mClientFrag.setRetainInstance(true);}returnmClientFrag;}

//HandlerforresponsemessagesfromtheserviceclassIncomingHandlerextendsHandler{@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseMessengerService.MSG_SET_SIMPLE_VALUE:updateStatus("Receivedfromservice:"+msg.arg1);break;default:break;}super.handleMessage(msg);}}

//NeedaMessengertoreceiveresponses.Sendthiswiththe//Messagestotheservice.finalMessengermMessenger=newMessenger(newIncomingHandler());

privateServiceConnectionmConnection=newServiceConnection(){publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){//Thisiscalledwhentheconnectionwiththeservicehasbeen//established,givingustheserviceobjectwecanuseto//interactwiththeservice.Weare

communicatingwithour//servicethroughaMessenger,sogetaclient-side//representationofthatfromtherawserviceobject.mService=newMessenger(service);updateStatus("Attached.");

//Wewanttomonitortheserviceforaslongasweare//connectedtoit.Thisisnotstrictlynecessary.You//donotneedtoregisterwiththeservicebeforeusing//it.Butifthisfailedyou'dhaveanearlywarning.try{Messagemsg=Message.obtain(null,MessengerService.MSG_REGISTER_CLIENT);msg.replyTo=mMessenger;mService.send(msg);}catch(RemoteExceptione){//Inthiscasetheservicehascrashedbeforewecouldeven//doanythingwithit;wecancountonsoonbeing//disconnected(andthenreconnectedifitcanberestarted)//sothereisnoneedtodoanythinghere.Log.e(TAG,"Couldnotestablishaconnectiontotheservice:"+e);}}

publicvoidonServiceDisconnected(ComponentNameclassName){//Thisiscalledwhentheconnectionwiththeservicehasbeen//unexpectedlydisconnected—thatis,itsprocesscrashed.mService=null;updateStatus("Disconnected.");}};

publicvoiddoBindService(){//Establishaconnectionwiththeservice.Weusethe

Stringname//oftheservicesinceitexistsinaseparateprocessandwedo//notwanttorequiretheservicejarintheclient.Wealsograb//theapplicationcontextandbindtheservicetothatsincethe//activitycontextcouldgoawayonaconfigurationchangebutthe//applicationcontextwillalwaysbethere.appContext=getActivity().getApplicationContext();if(mIsBound=appContext.bindService(newIntent("com.androidbook.messengerservice.MessengerService"),mConnection,Context.BIND_AUTO_CREATE)){updateStatus("Boundtoservice.");}else{updateStatus("Bindattemptfailed.");}}

publicvoiddoUnbindService(){if(mIsBound){//Ifwehavereceivedtheservice,andhenceregisteredwith//it,thennowisthetimetounregister.Notethatthe//replyTovalueisonlyusedbytheservicetounregister//thisclient.Noresponsemessagewillcomebacktotheclient.if(mService!=null){try{Messagemsg=Message.obtain(null,MessengerService.MSG_UNREGISTER_CLIENT);msg.replyTo=mMessenger;mService.send(msg);}catch(RemoteExceptione){//Thereisnothingspecialweneedtodoiftheservice//hascrashed.}}

//Detachourexistingconnection.appContext.unbindService(mConnection);mIsBound=false;updateStatus("Unbound.");}}

//Ifyoucansimplifyandsendonlyoneortwointegers,this//istheeasywaytodoit.publicvoiddoSendSimple(){try{Messagemsg=Message.obtain(null,MessengerService.MSG_SET_SIMPLE_VALUE,this.hashCode(),0);mService.send(msg);updateStatus("Sendingsimplemessage.");}catch(RemoteExceptione){Log.e(TAG,"Couldnotsendasimplemessagetotheservice:"+e);}}

//Ifyouhavemorecomplexdata,throwitintoaBundleand//addittotheMessage.CanalsopassParcelablesifyoulike.publicvoiddoSendComplex(){try{Messagemsg=Message.obtain(null,MessengerService.MSG_SET_COMPLEX_VALUE);BundlemBundle=newBundle();mBundle.putString("stringArg","Thisisastringtopass");mBundle.putDouble("myDouble",1138L);mBundle.putInt("myInt",42);msg.setData(mBundle);mService.send(msg);updateStatus("Sendingcomplexmessage.");}catch(RemoteExceptione){Log.e(TAG,"Couldnotsendacomplexmessagetotheservice:"+e);}}

privatevoidupdateStatus(Stringstatus){//MakesurethelateststatusisupdatedintheGUI,

which//ishandledbytheparentactivity.ISampleServiceClientuiContext=(ISampleServiceClient)getActivity();if(uiContext!=null){uiContext.updateStatus(status);}}}

Theclientfragmentcodeisfairlystraightforward.WhentheuserclickstheBindServicebutton,theclientfragmentbindstotheremoteserviceandsetsuptheServiceConnection.Bindingisdonefromtheapplicationcontext.Thisispreferredbecausefragmentscan’tbindservices,butactivitiesandapplicationscan.However,becausetheactivitycouldgoawayduringaconfigurationchange,it’sbettertobindtotheapplicationwhichwillalwaysbethere.WhentheServiceConnectiongetsconnected,anoutgoingMessengerissetuptosendtheMSG_REGISTER_CLIENTregisterclientmessagetotheservice.Theclientdoesnotwaitforareplyfromtheservicebutdoesgobacktowaitingfortheuser’snextinteraction.ThispreventsthedreadedANRpop-up.PressingSendSimplecreatesasimplemessageandsendsit.

Forasimplemessage,theservicedoesareplymessagewhichisreceivedbytheclient’shandlerandprocessed.AlltheclienthandlerdoesisupdatetheTextViewwiththevaluereceivedfromtheservice.Noticethattheclientfragmentusestheparentactivity’sISampleServiceClientinterfacetocallanappropriatemethodtoupdatetheUI.Thisisbecausetheclientfragmentisnon-UIandwewouldprefernottoembedUIlogicwithinit.Aninterfacekeepstheclientfragmentseparatefromtheactivityandmakesiteasytolettheactivitygoawayandcomebackduringaconfigurationchange.PressingSendComplexcreatesamessagewithabundlecontainingseveraldifferentvalues,whichissenttotheservice.Theservicewillusethedoublevalueinanotificationtoprovethatthevaluewasproperlytransmittedfromclienttoservice.Theservicedoesnotsendareplymessageforthecomplexmessage.

Onethingtobeawareofwiththismechanismforinter-processservicecalls:theservice’shandlerisworkingfromaqueueofincomingmessagesandisthereforesingle-threadedbydefault.Therewillnotbemultiplethreadsprocessingtheincomingservicemessagesunlessyoucreatesomeyourself.Sincetheclientisnotblockingonareplyfromtheservice,aclientapplicationwouldnotcrashiftheservicetookawhiletorespondtoamessage.However,itwouldbesomethingtokeepinmindifyou’llhavemultipleclientsoftheservice.AIDLservicescanmoreeasilyhandlerequestssimultaneously,soitmightbeabetterchoiceifyouneedmorepredictableresponsetimes.

Ifyourclientissendingmessagestomultipleservices,youcoulduseasingleMessenger/Handlerpairtoprocessreplymessagesfromthoseservices.YoujusthavetoputthatsameMessengerintoeachoutboundMessageandeachserviceshouldreplyback.

Theotherthingtobeawareofisthattheclienthasnoguaranteethattheservicewilleverrespond.TherearenotimeoutsinherentinaMessenger/Handlerinteraction.YouwillbenotifiedthroughonServiceDisconnected()iftheservicedies,butnotifithangs

ortakestoolong.Therefore,tobesurethattheservicerespondsinatimelyfashion,theclientcouldchoosetosetatimer,oranalarmtowakeitupagain.Ifthereplycomesbackbeforethetimer/alarmgoesoff,theclienthandlercouldclearit.Ifthetimer/alarmwakesuptheclient,itmeanstheservicetooktoolongandtheclientcouldthentakeappropriateaction.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch14_Services.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/:GreattutorialsonusingtheHttpClientclasses,includingauthenticationandtheuseofcookies.

http://developer.android.com/guide/components/bound-services.html:AndroidDeveloperGuideonBoundServices.

SummaryThischapterwasallaboutservices,specifically:

WetalkedaboutconsumingexternalHTTPservicesusingtheApacheHttpClient.

WithregardtousingtheHttpClient,weshowedyouhowtodoHTTPGETcallsandHTTPPOSTcalls.

WealsoshowedyouhowtodomultipartPOSTs.

YoulearnedthatSOAPcanbedonefromAndroid,butit’snotthepreferredwaytocallwebservices.

WetalkedabouthowyoucouldsetupanInternetproxytomanageaSOAPserviceonyourapplication’sbehalffromaserversomewhere,soyourapplicationcanuseRESTfulservicestoyourproxyandkeeptheapplicationsimpler.

Wethencoveredexceptionhandlingandthelikelytypesofexceptionsthatyourapplicationislikelytoexperience(timeoutsmostly).

YousawhowtousetheThreadSafeClientConnManagertoshareacommonHttpClientinsideyourapplication.

Youlearnedhowtocheckandsettimeoutvaluesforconnectionstothenetwork.

Wecoveredacoupleofoptionsformakingconnectionstowebservices,includingHttpURLConnectionandAndroidHttpClient.

Weexplainedthedifferencebetweenlocalservicesandremoteservices.Localservicesareservicesthatareconsumedbythecomponents(suchasactivities)inthesameprocessastheservice.Remoteservicesareserviceswhoseclientsareoutsidetheprocesshostingtheservices.

Youlearnedthateventhoughaserviceismeanttobeonaseparatethread,itisstilluptothedevelopertocreateandmanagethebackgroundthreadsassociatedwithservices.

Youdiscoveredhowtostartandstoplocalservices,andhowtocreateandbindtoaremoteservice.

YousawhowtheNotificationManagerisusedtotrackrunningservices.

Wecoveredhowtopassdatatoaservice,usingParcelablesforthecomplextypes.

YoulearnedhowtouseMessengersandHandlerstocallremoteservices.

Chapter15

AdvancedAsyncTaskandProgressDialogsInChapter13,wecoveredhandlersandworkerthreadstorunlong-runningtaskswhilethemainthreadkepttheUIhouseinorder.AndroidSDKhasrecognizedthisasapatternandabstractedthehandlerandthreaddetailsintoautilityclasscalledAsyncTask.YoucanuseAsyncTasktoruntasksthattakelongerthanfivesecondsinthecontextofUI.(Wewillcoverhowtorunreallylong-runningtasks,rangingfromminutestoevenhours,through“Long-RunningReceiversandServices”inChapter16.)

ThischapterwillstartwiththebasicsofanAsyncTaskandmovetothecodeneededtopresentprogressdialogsandprogressbarsthatshowthestatusofanAsyncTaskcorrectlyevenifthedevicechangesitsconfiguration.Let’sstartbyintroducingtheAsyncTaskthroughpseudocodeinListing15-1.

Listing15-1.UsagePatternforanAsyncTaskbyanActivity

publicclassMyActivity{voidrespondToMenuItem(){//menuhandlerperformALongTask();}voidperformALongTask(){//usinganAsyncTask

//DerivefromanAsyncTask,andInstantiatethisAsyncTaskMyLongTaskmyLongTask=newMyLongTask(...CallBackObjects…);myLongTask.execute(...someargs…);//starttheworkonaworkerthread//havethemainthreadgetbacktoitsUIbusiness}

//HearbackfromtheAsyncTaskvoidsomeCallBackFromAsyncTask(SomeParameterizedTypex){

//AlthoughinvokedbytheAsyncTaskthiscoderunsonthemainthread.//reportbacktotheuseroftheprogress}}

UseofanAsyncTaskstartswithextendingfromAsyncTaskfirstliketheMyLongTaskinListing15-1.OnceyouhavetheAsyncTaskobjectinstantiated,youcancallexecute()methodonthatobject.Theexecute()methodinternallystartsaseparatethreadtodotheactualwork.TheAsyncTaskimplementationwillinturn

invokeanumberofcallbackstoreportthebeginningofthetask,theprogressofthetask,andtheendofthetask.Listing15-2showspseudocodetoextendanAsyncTaskandthemethodsthatneedtobeoverridden.(Pleasenotethatthisispseudocodeandnotintendedtobecompiled.The@overrideannotationisaddedtoexplicitlystatethattheyareoverriddenfromthebaseclass.)

Listing15-2.ExtendinganAsyncTask:AnExample

publicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>{

//...constructorsstuff//Callingexecute()willresultincallingallofthesemethods@Override

voidonPreExecute(){}//Runsonthemainthread

//Thisiswhereyoudoalltheworkandrunsontheworkerthread@Override

IntegerdoInBackground(String…params){}

//Runsonthemainthreadagainonceitfinishes@Override

voidonPostExecute(Integerresult){}

//Runsonthemainthread@Override

voidonProgressUpdate(Integer…progressValuesArray){}

//....othermethods}

execute()methodinListing15-1iscalledonthemainthread.ThiscallwilltriggeraseriesofmethodsinListing15-2,startingwithonPreExecute().TheonPreExecute()iscalledonthemainthreadaswell.Youcanusethismethodtosetupyourenvironmenttoexecutethetask.Youcanalsousethismethodtosetupadialogboxorinitiateaprogressbartoindicatetotheuserthattheworkhasstarted.AfterthecompletionoftheonPreExecute(),execute()methodwillreturnandthemainthreadoftheactivitycontinueswithitsUIresponsibilities.Bythattimetheexecute()wouldhavespawnedanewworkerthreadsothatdoInBackground()methodisscheduledtobeexecutedonthatworkerthread.YouwilldoallyourheavyliftinginthisdoInBackground()method.Asthismethodrunsonaworkerthread,themainthreadisnotaffectedandyouwillnotgetthe“applicationnotresponding”message.FromthedoInBackground()methodyouhaveafacility(youwillseethisshortly)tocalltheonProgressUpdate()toreporttheprogress.ThisonProgressUpdate()methodrunsonthemainthreadsothatyoucanaffecttheUIonthemainthread.

EssentialsofaSimpleAsyncTaskLet’sgetintothedetailsofextendingtheAsyncTask.TheAsyncTaskclassuses

genericstoprovidetypesafetytoitsmethods,includingtheoverriddenmethods.Youcanseethesegenericswhenyoulookatthepartialdefinition(Listing15-3)oftheAsyncTaskclass.(PleasenotethatListing15-3isanextremelypruned-downversionoftheAsyncTaskclass.It’sreallyjusttheelementsofitsinterfacemostcommonlyusedbyclientcode.)

Listing15-3.AQuickLookattheAsyncTaskClassDefinition

publicclassAsyncTask<Params,Progress,Result>{

//AclientwillcallthismethodAsyncTask<Params,Progress,Result>execute(Params…params);

//Doyourworkhere.FrequentlytriggersonProgressUpdate()ResultdoInBackGround(Params…params);

//Callback:AftertheworkiscompletevoidonPostExecute(Resultresult);

//Callback:AstheworkisprogressingvoidonProgressUpdate(Progress…progressValuesArray);

}

StudyingListing15-3,youcanseethattheAsyncTask(throughgenerics)needsthefollowingthreeparameterizedtypes(Params,Progress,andResult)whenyouextendit.Let’sexplainthesetypesbriefly:

Params(Thetypeofparameterstotheexecute()method):WhenextendingAsyncTask,youwillneedtoindicatethetypeofparametersthatyouwillpasstotheexecute()method.IfyousayyourParamstypeisString,thentheexecute()methodwillexpectanynumberofstringsseparatedbycommasinitsinvocationsuchasexecute(s1,s2,s3)orexecute(s1,s2,s3,s4,s5).

Progress(Parametertypestotheprogresscallbackmethod):ThistypeindicatesthearrayofvaluespassedbacktothecallerwhilereportingprogressthroughthecallbackonProgressUpdate(Progress…progressValuesArray).Theabilitytopassanarrayofprogressvaluesallowssituationswheremultipleaspectsofataskcanbemonitoredandreportedon.Forexample,thisfeaturecouldbeusedifanAsyncTaskisworkingonmultiplesubtasks.

Result(TypeusedtoreporttheresultthroughonPostExecute()method):ThistypeindicatesthetypeofthereturnvaluethatissentbackasthefinalresultfromtheexecutionthroughthecallbackonPostExecute(Result

finalResult).

KnowingnowtheneededgenerictypesforanAsyncTask,supposewedecideonthefollowingparametersforourspecificAsyncTask:Params:AString,Result:Anint,Progress:AnInteger.Then,wecandeclareanextendedAsyncTaskclassasshowninListing15-4.

Listing15-4.ExtendingtheGenericAsyncTaskThroughConcreteTypes

publicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>

{//...otherconstructorsstuff//...othermethods//ConcretemethodsbasedontheparameterizedtypesprotectedIntegerdoInBackground(String…params);protectedvoidonPostExecute(Integerresult);protectedvoidonProgressUpdate(Integer…progressValuesArray);

//....othermethods}

NoticehowthisconcreteclassinListing15-4,MyLongTask,hasdisambiguatedthetypenamesandarrivedatfunctionsignaturesthataretypesafe.

ImplementingYourFirstAsyncTaskLet’snowlookatasimple,butcomplete,implementationofMyLongTask.WehaveamplycommentedthecodeinListing15-5inlinetoindicatewhichmethodsrunonwhichthread.AlsopayattentiontotheconstructorofMyLongTaskwhereitreceivesobjectreferencesofthecallingcontext(usuallyanactivity)andalsoaspecificsimpleinterfacesuchasIReportBacktologprogressmessages.

TheIReportBackinterfaceisnotcriticaltoyourunderstandingbecauseitismerelyawrappertoalog.SameistruewiththeUtilsclassaswell.Youcanseetheseadditionalclassesinbothofthedownloadableprojectsforthischapter.TheURLforthedownloadableprojectsisinthereferencessectionattheendofthischapter.Listing15-5showsthecompletecodeforMyLongTask.

Listing15-5.CompleteSourceCodeforImplementinganAsyncTask

//ThefollowingcodeisinMyLongTask.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync1toinvokethiscodepublicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>

{IReportBackr;//aninterfacetoreportbacklog

messagesContextctx;//TheactivitytostartadialogpublicStringtag=null;//DebugtagProgressDialogpd=null;//Tostart,report,andstopaprogressdialog

//ConstructornowMyLongTask(IReportBackinr,ContextinCtx,StringinTag){r=inr;ctx=inCtx;tag=inTag;}//RunsonthemainuithreadprotectedvoidonPreExecute(){

Utils.logThreadSignature(this.tag);pd=ProgressDialog.show(ctx,"title","InProgress…",true);}//Runsonthemainuithread.TriggeredbypublishProgresscalledmultipletimesprotectedvoidonProgressUpdate(Integer…progress){

Utils.logThreadSignature(this.tag);Integeri=progress[0];r.reportBack(tag,"Progress:"+i.toString());}protectedvoidonPostExecute(Integerresult){

//RunsonthemainuithreadUtils.logThreadSignature(this.tag);r.reportBack(tag,"onPostExecuteresult:"+result);pd.cancel();}//Runsonaworkerthread.Mayevenbeapooliftherearemoretasks.protectedIntegerdoInBackground(String…strings){

Utils.logThreadSignature(this.tag);for(Strings:strings){Log.d(tag,"Processing:"+s);}for(inti=0;i<3;i++){Utils.sleepForInSecs(2);publishProgress(i);//thiscallsonProgressUpdate}return1;//thisvalueisthenpassedtotheonPostExecuteasinput}}

WewillgointothedetailsofeachofthemethodshighlightedinListing15-5after

coveringbrieflyhowaclientwouldmakeuseof(orcall)MyLongTask.

CallinganAsyncTaskOncewehavetheclassMyLongTaskimplemented,aclientwillutilizethisclassasshowninListing15-6.

Listing15-6.CallinganAsyncTask

//YouwillfindthisclassAsyncTester.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync1toinvokethiscodevoidrespondToMenuItem(){//Aninterfacetologsomemessagesbacktotheactivity//Seedownloadableprojectifyouneedthedetails.IReportBackreportBackObject=this;Contextctx=this;//activityStringtag="Task1";//debugtag

//InstantiateandexecutethelongtaskMyLongTaskmlt=newMyLongTask(reportBackObject,ctx,tag);

mlt.execute("String1","String2","String3");

}

Noticehowtheexecute()methodiscalledinListing15-6.BecausewehaveindicatedoneofthegenerictypesasaStringandthattheexecute()methodstakesavariablenumberofargumentsforthistype,wecanpassanynumberofstringstotheexecute()method.IntheexampleinListing15-6,wehavepassedthreestringarguments.Youcanpassmoreorlessasyouneed.

Oncewecalltheexecute()methodontheAsyncTask,thiswillresultinacalltotheonPreExecute()methodfollowedbyacalltothedoInBackground()method.ThesystemwillalsocalltheonPostExecute()callbackoncethedoInBackground()methodfinishes.RefertoListing15-5forhowthesemethodsareimplemented.

UnderstandingtheonPreExecute( )CallbackandProgressDialogGoingbacktoMyLongTaskimplementationinListing15-5,intheonPreExecute()methodwestartedaprogressdialogtoindicatethatthetaskisinprogress.Figure15-1showsanimageofthatdialog.(UsemenuitemTestAsync1toinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTask.zip.)

Figure15-1.AsimpleprogressdialoginteractingwithanAsyncTask

Thecodesegment(takenfromListing15-5)thatshowstheprogressdialogisreproducedinListing15-7.

Listing15-7.ShowinganIndeterminateProgressDialog

pd=ProgressDialog.show(ctx,"title","InProgress…",true);

Thevariablepdwasalreadydeclaredintheconstructor(seeListing15-5).ThiscallinListing15-7willcreateaprogressdialoganddisplayitasshowninFigure15-1.Thelastargumenttotheshow()methodinListing15-7indicatesifthedialogisindeterminate(whetherthedialogcanestimatebeforehandhowmuchworkthereis).Wewillcoverthedeterministiccaseinalatersection.

NoteShowingprogressofanAsyncTaskreliablyisquiteinvolved.Thisisbecauseanactivitycancomeandgo,becauseofeitheraconfigurationchangeoranotherUItakingprecedence.Wewillcoverthisessentialneedandsolutionslaterinthechapter.

UnderstandingthedoInBackground( )MethodAllthebackgroundworkcarriedoutbytheAsyncTaskisdoneinthedoInBackground()method.ThismethodisorchestratedbytheAsyncTasktorunonaworkerthread.Asaresult,thisworkisallowedtotakemorethanfiveseconds,unliketheworkdoneonamainthread.

InourexamplefromListing15-5,inthedoInBackground()methodwesimplyretrieveeachoftheinputstringstothetaskasiftheyareanarray.Inthismethoddefinitionwehaven’tdefinedanexplicitstringarray.However,thesingleargumenttothisfunctionisdefinedasavariable-lengthargument,asshowninListing15-8.

Listing15-8.doInBackground()MethodSignature

protectedIntegerdoInBackground(String…strings)

Javathentreatstheargumentasifitisanarrayinsidethefunction.SoinourcodeinthedoInBackground()method,wereadeachofthestringsandlogthemtoindicatethatweknowwhattheyare.Wethenwaitlongenoughtosimulatealong-runningoperation.Becausethismethodisrunninginaworkerthread,wehavenoaccesstotheUIfunctionalityofAndroidfromthisworkerthread.Forinstance,youwon’tbeabletoupdateanyViewsdirectlyevenifyouhaveaccesstothemfromthisthread.YoucannotevensendaToastfromhere.Thenexttwomethodsallowustoovercomethis.

TriggeringonProgressUpdate( )throughpublishProgress( )InthedoInBackground()method,youcantriggeronProgressUpdate()bycallingthepublishProgress()method.ThetriggeredonProgressUpdate()methodthenrunsonthemainthread.ThisallowstheonProgressUpdate()methodtoupdateUIelementssuchasViewsappropriately.YoucanalsosendaToastfromhere.InListing15-5,wesimplylogamessage.Oncealltheworkisdone,wereturnfromthedoInBackground()methodwitharesultcode.

UnderstandingtheonPostExecute( )MethodTheresultcodefromthedoInBackground()methodisthenpassedtotheonPostExecute()callbackmethod.Thiscallbackisalsoexecutedonthemainthread.Inthismethod,wetelltheprogressdialogtoclose.Beingonthemainthread,youcanaccessanyUIelementsinthismethodwithnorestrictions.

UpgradingtoaDeterministicProgressDialogInthepreviousexampleinListing15-5,weusedaprogressdialog(Figure15-1)thatdoesn’ttelluswhatportionoftheworkiscomplete.Thisprogressdialogiscalledan

indeterminateprogressdialog.Ifyousettheindeterminatepropertytofalseonthisprogressdialog,youwillseeaprogressdialogthattracksprogressinsteps.ThisisshowninFigure15-2.(Usemenuitem“TestAsync2”toinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTask.zip.)

Figure15-2.Aprogressdialogshowingexplicitprogress,interactingwithanAsyncTask

Listing15-9showstheprevioustaskfromListing15-5rewrittentochangethebehavioroftheprogressdialogtoadeterministicprogressdialog.WehavealsoaddedanonCancelListenertoseeifweneedtocancelthetaskoncancellingthedialog.AusercanclickthebackbuttoninFigure15-2tocancelthedialog.KeyportionsofthecodearegiveninListing15-9(forthefullcode,seethedownloadfileProAndroid5_Ch15_TestAsyncTask.zip).

Listing15-9.ALongTaskUtilizingaDeterministicProgressDialog

//FollowingcodeisinMyLongTask1.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync2toinvokethiscode

publicclassMyLongTask1extendsAsyncTask<String,Integer,Integer>implementsOnCancelListener{//..othercodetakenfromListing15-5//AlsorefertothejavaclassMyLongTask1.javainthedownloadableproject//forfullcodelisting.protectedvoidonPreExecute(){//....othercodepd=newProgressDialog(ctx);pd.setTitle("title");pd.setMessage("InProgress…");pd.setCancelable(true);pd.setOnCancelListener(this);pd.setIndeterminate(false);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMax(5);pd.show();}publicvoidonCancel(DialogInterfaced){r.reportBack(tag,"CancelCalled");this.cancel(true);}//..othercodetakenfromListing15-5}

NoticehowwehavepreparedtheprogressdialoginListing15-9.Inthiscasewehaven’tusedthestaticmethodshow(),incontrasttowhatwedidinListing15-5,ontheprogressdialog.Instead,weexplicitlyinstantiatedtheprogressdialog.Thevariablectxstandsforthecontext(oractivity)inwhichthisUIprogressdialogoperates.Thenweindividuallysetthepropertiesonthedialog,includingitsdeterministicorindeterminatebehavior.ThemethodsetMax()indicateshowmanystepstheprogressdialoghas.Wehavealsopassedtheselfreference(theAsyncTaskitself)asalistenerwhencancelistriggered.Inthecancelcallback,weexplicitlyissueacancelontheAsyncTask.Thecancel()methodwilltrytostoptheworkerthreadifwecallitwiththebooleanargumentoffalse.Abooleanargumentoftruewillforce-stoptheworkerthread.

AsyncTaskandThreadPoolsConsiderthecodeinListing15-10,whereamenuitemisinvokingtwoAsyncTasksoneaftertheother.

Listing15-10.InvokingTwoLong-RunningTasks

voidrespondToMenuItem(){

MyLongTaskmlt=newMyLongTask(this.mReportTo,this.mContext,"Task1");mlt.execute("String1","String2","String3");

MyLongTaskmlt1=newMyLongTask(this.mReportTo,this.mContext,"Task2");mlt1.execute("String1","String2","String3");}

Hereweareexecutingtwotasksonthemainthread.Youmayexpectthatboththetasksgetstartedclosetoeachother.Thedefaultbehavior,however,isthatthesetasksrunsequentiallyusingasinglethreaddrawnoutofapoolofthreads.Ifyouwantaparallelexecution,youcanusetheexecuteOnExecutor()methodontheAsyncTask.SeeSDKdocsfordetailsonthismethod.AlsoaspertheSDKdocumentation,itisnotvalidtocalltheexecute()methodmorethanonceonasingleAsyncTask.Ifyouwantthatbehavior,youhavetoinstantiateanewtaskandcalltheexecute()methodagain.

IssuesandSolutionsforCorrectlyShowingtheProgressofanAsyncTaskIfyourprimarygoalwiththischapteristolearnjusttheessentialsofAsyncTask,thenwhatwehavecoveredsofarissufficient.However,therearesomeissueswhenanAsyncTaskispairedwithaprogressdialogasshowninthepreviouslistingssofar.OneofthoseissuesisthatanAsyncTaskwilllosethecorrectactivityreferencewhenthedeviceisrotated,therebyalsolosingitsreferencetotheprogressdialog.Theotherissueisthattheprogressdialogweusedearlierinthecodeisnotamanageddialog.Let’sunderstandtheseissuesnow.

DealingwithActivityPointersandDeviceRotationTheactivitypointerthatisheldbytheAsyncTaskbecomesstalewhentheactivityisre-createdbecauseofaconfigurationchange.ThisisbecauseAndroidhascreatedanewactivityandtheoldactivityisnolongershownonthescreen.Soholdingontotheoldactivityanditscorrespondingdialogisbadforacoupleofreasons.ThefirstisthattheuserisnotseeingthatactivityordialogthattheAsyncTaskistryingtoupdate.ThesecondreasonisthattheoldactivityneedstobegarbagecollectedandyouarestoppingitfromgettinggarbagecollectedbecausetheAsyncTaskisholdingontoitsreference.IfyouweretobesmartanduseaJavaweakreferencefortheoldactivity,thenyouwouldn’tleakmemorybutyouwouldgetanullpointerexception.Thecaseofstalepointeristruenotonlyoftheactivitypointerbutanyotherpointerthatindirectlypointstotheactivity.

Therearetwowaystoaddressthestaleactivityreferenceissue.Therecommendedwayistouseheadlessretainedfragments.(FragmentsarecoveredinChapter8.Retainedfragmentsarefragmentsthatstayaroundwhiletheactivityisre-createdduetoa

configurationchange.Thesefragmentsarealsocalledheadlessbecausetheydon’tnecessarilyhavetoholdanyUI.)Anotherwaytosolvethestaleactivitypointersistousetheretainedobjectscallbackfromtheactivity.Wewillpresentbothoftheseapproachesforaddressingthestaleactivitypointerissue.

DealingwithManagedDialogsEvenifweareabletosolvethestaleactivityreferenceissueandreestablishtheconnectivitytothecurrentactivity,thereisaflawinthewayprogressdialogswereusedsofarinthischapter.WehaveinstantiatedaProgressDialogdirectly.AProgressDialogcreatedinthismannerisnota“managed”dialog.Ifitisnotamanageddialog,theactivitywillnotre-createthedialogwhenthedeviceundergoesrotationoranyotherconfigurationchange.So,whenthedevicerotatestheAsyncTaskisstillrunninguninterruptedbutthedialogwillnotshowup.Thereareacoupleofwaystosolvethisproblemaswell.TherecommendedwayisnottouseprogressdialogsbutinsteaduseanembeddedUIcontrolintheactivityitself,suchasaprogressbar.Becauseaprogressbarispartoftheactivityviewhierarchy,thehopeisthatitwillbere-created.Althoughaprogressbarsoundsgood,therearetimeswhenamodalprogressdialogmakesmoresense.Forexample,thatwouldbethecaseifyoudon’twanttheusertointeractwithanyotherpartoftheactivitywhiletheAsyncTaskisrunning.Inthosecases,weseelittlecontradictioninusingfragmentdialogsinsteadofprogressbars.

It’stimewestepintothesolutionstodealwiththeactivityreferencesissuesandthemanageddialogsissue.Wewillpresentthreedifferentsolutions.Thefirstoneusesretainedobjectsandfragmentdialogs.Thesecondoneusesheadlessretainedfragmentsandfragmentdialogs.Thethirdsolutionusesheadlessretainedfragmentsandprogressbars.

TestingScenariosforaWell-BehavedProgressDialogOfthethreesolutionswehaveinthischapter,whicheveryouusetocorrectlydisplayaprogressdialogforanAsyncTask,thesolutionshouldworkinallofthefollowingtestscenarios:

1. Withoutanorientationchangetheprogressdialogmuststart,showitsprogress,end,andalsocleanupthereferencetotheAsyncTask.Thismustworkrepeatedlytoshowthattherearenovestigesleftfromthepreviousrun.

2. Thesolutionshouldhandletheorientationchangeswhilethetaskisinthemiddleofitsexecution.Therotationshouldre-createthedialogandshowprogresswhereitleftoff.ThedialogshouldproperlyfinishandcleanuptheAsyncTaskreference.Thismustworkrepeatedlytoshowthattherearenovestigesleftbehind.

3. Thebackshouldbedisabledwhenthetaskstartstorun.

4. GoingHomeshouldbeallowedevenwhenthetaskisinthemiddle

ofexecution.

5. GoingHomeandrevisitingtheactivityshouldshowthedialogandcorrectlyreflectthecurrentprogress,andtheprogressshouldneverbelessthantheonebefore.

6. GoingHomeandrevisitingtheactivityalsoshouldworkwhenthetaskfinishesbeforereturning.ThedialogshouldbeproperlydismissedandtheAsyncTaskreferenceremoved.

ThissetoftestcasesshouldalwaysbeperformedforallactivitiesdealingwithAsyncTasks.Nowthatwehavelaidouthoweachsolutionshouldsatisfy,let’sstartwiththefirstsolution,theonethatusesretainedobjectsandfragmentdialogs.

UsingRetainedObjectsandFragmentDialogsInthisfirstsolution,let’sshowyouhowtouseretainedobjectsandfragmentdialogsfordisplayingprogresscorrectlyforanAsyncTask.Thissolutioninvolvesthefollowingsteps:

1. TheactivitymustkeeptrackofanexternalobjectthroughitsonRetainNonConfigurationInstance()callback.Thisexternalobjectmuststickaroundanditsreferencevalidatedastheactivityisclosedandbroughtback.Thatiswhythisobjectisreferredtoasaretainedobject.ThisretainedobjectcaneitherbetheAsyncTaskobjectitselforanintermediateobjectthatholdsareferencetotheAsyncTask.Let’scallthatarootretainedactivity-dependentobject(orarootRADO).Itiscalleda“root”becausetheonRetainNonConfigurationInstance()canuseonlyoneretainedobjectreference.

2. ArootRADOthenwillhaveapointertotheAsyncTaskandcansetandresettheactivitypointeronAsyncTaskastheactivitycomesandgoes.So,thisrootRADOactsasanintermediarybetweentheactivityandtheAsyncTask.

3. TheAsyncTaskthenwillinstantiateafragmentprogressdialoginsteadofaplainnon-managedprogressdialog.TheAsyncTaskwillusetheactivitypointerthatissetbytherootRADOtoaccomplishthis,asyouwillneedanactivitytocreateafragmentincludingafragmentdialog.

4. Theactivitywillre-createthedialogfragmentasitrotatesandkeepsitsstateproperlybecausedialogfragmentsaremanaged.TheAsyncTaskcanproceedtoincrementprogressonthefragmentdialogaslongastheactivityissetandavailable.Notethatthisdialogfragmentisnot,byitself,aretainedfragment.Itgetsre-createdaspartoftheactivitylifecycle.

5. ThefragmentdialogcanfurtherdisallowthecancelonitsothattheusercannotgobacktotheactivityfromthedialogwhiletheAsyncTaskisinprogress.

6. However,ausercangoHomebytappingHomeanduseotherapps.Thiswillpushouractivity,andthedialogwithit,intothebackground.Thismustbehandled.Whentheuserreturnstotheactivityorapp,thedialogcancontinuetoshowtheprogress.TheAsyncTaskmustknowhowtodismissthefragmentdialogifthetaskfinisheswhiletheactivityishidden.Beingafragmentdialog,dismissingthisdialogthrowsaninvalidstateexceptioniftheactivityisnotintheforeground.So,theAsyncTaskhastowaituntiltheactivityisreopenedandintherightstatetodismissthedialog.

ExploringCorrespondingKeyCodeSnippetsWewillpresentnowthekeypiecesofcodethatareusedtoimplementtheoutlinedapproach.TherestoftheimplementationcanbefoundinthedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipforthischapter.Asallsolutionsforthisproblemrequirethedialogtobeafragmentdialog,sothatthedialogcanbemanaged,Listing15-11presentsthesourcecodeofthisfragmentdialogfirst.

Listing15-11.EncapsulatingaProgressDialoginaDialogFragment

//ThefollowingcodeisinProgressDialogFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)/***ADialogFragmentthatencapsulatesaProgressDialog.*Thisisnotexpectedtobearetainedfragmentdialog.*Getsre-createdasactivityrotatesfollowinganyfragmentprotocol.*/publicclassProgressDialogFragmentextendsDialogFragment{

privatestaticStringtag="ProgressDialogFragment";ProgressDialogpd;//WillbesetbyonCreateDialog

//ThisgetscalledfromADOssuchasretainedfragments//typicallydonewhenactivityisattachedbacktotheAsyncTaskprivateIFragmentDialogCallbacksfdc;publicvoidsetDialogFragmentCallbacks(IFragmentDialogCallbacks

infdc){

Log.d(tag,"attachingdialogcallbacks");fdc=infdc;}

//Thisisadefaultconstructor.Calledbytheframework

allthetime//forreintroduction.publicProgressDialogFragment(){

//Shouldbesafeformetosetcancelableasfalse;//wonderifthatiscarriedthroughrebirth?this.setCancelable(false);}//Onewayfortheclienttoattachinthebeginningwhenthefragmentisreborn.//ThereattachmentisdonethroughsetFragmentDialogCallbacks//Thisisashortcut.Yourcompilerifenabledforlintmaythrowanerror.//YoucanusethenewInstancepatternandsetbundle(seethefragmentschapter)publicProgressDialogFragment(IFragmentDialogCallbacksinfdc){

this.fdc=infdc;this.setCancelable(false);}/***Thiscangetcalledmultipletimeseachtimethefragmentis*re-created.Sostoringthedialogreferenceinalocalvariableshouldbesafe*/@OverridepublicDialogonCreateDialog(BundlesavedInstanceState){

Log.d(tag,"InonCreateDialog");pd=newProgressDialog(getActivity());pd.setTitle("title");pd.setMessage("InProgress…");pd.setIndeterminate(false);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMax(15);returnpd;}//Calledwhenthedialogisdismissed.Ishouldtellmycorrespondingtask//tocloseordotherightthing!Thisisdonethroughcallbacktofdc//fdc:fragmentdialogcallbackscouldbetheTask,orActivityortherootRADO//SeeListing15-12toseehowFDCisimplementedbythetask@OverridepublicvoidonDismiss(DialogInterfacedialog){

super.onDismiss(dialog);

Log.d(tag,"Dialogdismissed");if(fdc!=null){fdc.onDismiss(this,dialog);}}@OverridepublicvoidonCancel(DialogInterfacedialog){

super.onDismiss(dialog);Log.d(tag,"Dialogcancelled");if(fdc!=null){fdc.onCancel(this,dialog);}}//willbecalledbyaclientlikethetaskpublicvoidsetProgress(intvalue){

pd.setProgress(value);}}

CodeinListing15-11showshowtowraparegularnon-managedProgressDialoginamanagedfragmentdialog.WeextendaDialogFragmentandoverrideitsonCreateDialog()toreturntheProgressDialogobject.Inadditiontothatbasicfeature,weaddedtheabilitytomonitorwhentheprogressdialoggetsdismissedorcancelled.WealsoprovideasetProgress()methodonthewrappedclasstocallthesetProgress()ontheinternalProgressDialog.YoucanseethesourcecodefortheIFragmentDialogCallbacksinthedownloadableproject(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip),asitisnotthatcriticaltounderstandingthisfragmentprogressdialog.

Let’sseenowhowanAsyncTaskcancreateandcontrolthisfragmentprogressdialog.Listing15-12presentsthepseudocodefortheAsyncTaskinordertoaidthisunderstanding.Forthecompletesourcecode,refertodownloadableproject.

Listing15-12.PseudocodeforanAsyncTaskThatUsesaFragmentProgressDialog

//ThefollowingcodeisinMyLongTaskWithRADO.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithADOspublicclassMyLongTaskWithRADOextendsAsyncTask<String,Integer,Integer>implementsIRetainedADO,IFragmentDialogCallbacks{//....othercode@OverridepublicvoidonPreExecute(){

//....othercode//gettheactivityasitwouldhavebeensetbytherootRADO

Activityact=this.getActivity();

//CreatetheprogressdiaolgProgressDialogFragmentpdf=newProgressDialogFragment();//theshowmethodwilladdandcommitthefragmentdialogpdf.show(act.getFragmentManager(),this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);}@OverridepublicvoidonProgressUpdate(){

//ifactivityisavailable,getthefragmentdialogfromit//callsetProgress()onit//otherwiseignoretheprogress}@OverridepublicvoidonPostExecute(){

//ifactivityisinagoodstate//dismissthedialogandtelltherootRADOtodropthepointertotheAsyncTask//ifnotrememberitthroughaflagtocloseitwhenyoucomeback}@Overridepublicvoidattach(){

//calledwhentheactivityisback//checktoseeifyouaredone//ifsodismissthedialogandremoveyourselffromtheRADO//ifnotcontinuetoupdatetheprogress}}

BecausethisAsyncTaskimplementstheideaofaretainedactivity-dependentobject(IRetainedADO),itknowswhentheactivityisavailableandwhenitisnot.Italsoknowsthestateoftheactivity,suchaswhethertheUIisreadyornot.Althoughittakessomecodetoimplementactivity-dependentobjects(ADOs),itisnotahardconcept.WeleaveittoyouduetospaceconsiderationstolookintothedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipandseehowthisisdone.

ThisAsyncTaskinListing15-12alsotakesoverthemanagementofitsfragmentdialogsothatitactslikeacohesiveunitandtherebydoesn’tcontaminatethemainactivitywiththedetailsofthisAsyncTask.AnotherkeydetailinListing15-12iswhathappenswhenthedialogisdismissedastheAsyncTaskfinishes.Atthisinstantiftheactivityishidden,ornotthereduetorotation,itisimportanttodismissthedialogwhentheactivityisre-created.Inordertodothis,theonPostExecute()remembersthelaststateoftheAsyncTaskwhetheritisdoneorinprogress.ThisAsyncTaskthenwaitsforthe

attach()method,whichgetscalledwhentheUIreadyactivityisreattachedtothisADO.Onceintheattach()method,theAsyncTaskcanthendismissthefragmentdialog.

YoucandownloadtheprojectnamedProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipandseehowtheinteractionpresentedinListing15-12isfullyrealized.

Thisparticularapproachofusingretainedobjectsisabitinvolvedwhencomparedtousingretainedfragmentsinstead.ButithastheeleganceofsolvingitinamoregenericformusingtheideaofADOs,betheyfragmentsorotherwise.Wehavelinksinthereferencesectionthatoutlinethisideaandprovidebackground.Withthat,let’sturnourattentiontotherecommendedideaofretainedfragmentsinoursecondsolution.

UsingRetainedFragmentsandFragmentDialogsInthesecondsolution,wewillstickwiththefragmentdialogsbutwewilluseheadlessretainedfragmentsinsteadofsimpleretainedobjects.Androidhasdeprecatedtheretainedobjectsinfavorofretainedfragments.InAndroidaretainedobjectisjustanobjectandhasnoin-builtabilitytotrackthestateoftheactivity.(ThisiswhywehadtoinventtheframeworkofADOsontop.)ThisdeficiencyisnottherewiththeintroductionoffragmentsinlaterreleasesofAndroid.AlthoughfragmentsaretightlywovenintothefabricofUI,theycanexistwithoutUIaswell.Thesearecalledheadlessfragments.Inadditiontobeingabletotrackthestateoftheactivity,fragmentscanalsoberetained,muchlikeretainedobjects.

OutliningtheRetainedFragmentsApproachTheapproachinthissolutionistouseaheadlessretainedfragmentasananchortocommunicatebetweentheactivityandtheAsyncTask.Herearethekeyaspectsofthisapproach:

1. Continuetouseafragmentprogressdialog,asinthesolutionbefore.

2. HavetheactivitycreateaheadlessretainedfragmentwhichthenholdsapointertotheAsyncTask.Thisheadlessretainedfragmenttakestheplaceoftheretainedobjectintheprevioussolution.Beingaretainedfragment,thefragmentobjectsticksaroundwhiletheactivityisre-createdwithanewpointer.TheAsyncTaskthenalwaysreliesontheretainedfragmenttoretrievethemostcurrentactivitypointer.

3. TheAsyncTaskreliesontheheadlessretainedfragmenttobeinformedoftheactivitystatetoaccomplishallofthetestcasesindicatedintheprevioussolution.

ExploringCorrespondingKeyCodeSnippets

Wehavealreadyshownyouthecodeforthefragmentdialogduringtheprevioussolution.Aswecontinuetousethesameobjectinthissolution,wewillfocusontheretainedfragmentandalsohowtheAsyncTaskusesthefragmentdialogthroughtheretainedfragment.

Inthesampleprogram(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)wehaveprovidedinthedownload,wecalledtheretainedfragmentAsyncTesterFragment.Listing15-13showsthepseudocodeforthisclass,whichdemonstrates,amongotherthings,whatmakesthisclassaheadlessfragment.

Listing15-13.PseudocodeforaHeadlessFragment

//ThefollowingcodeisinAsyncTesterFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicclassAsyncTesterFragment

extendsFragment(oranotherobjectthatisderivedfromFragment){//NoneedtooverridethekeyonCreateView()method

//whichotherwisewouldhavereturnedaviewloadedfromalayout.//ThushavingnoViewmakesthisfragmentaheadlessfragment

//UsethisnametoregisterwiththeactivitypublicstaticStringFRAGMENT_NAME=“AsyncTesterRetainedFragment”;

//Localvariablefortheasynctask.Youcanuseamenutostartworkonthistask//NullifythisreferencewhentheasynctaskfinishesMyLongTaskWithFragmentDialogtaskReference;

//Haveaninitmethodtohelpwithinheritancepublicvoidinit(arg1,arge2,etc){super.init(arg1,…);//ifthereisonesetArguments(….);//orpassthebundletothesuperinit}publicstaticAsyncTesterFragmentnewInstance(arg1,arg2,…){AsyncTesterFragmentf=newAsyncTesterFragment();f.init(arg1,arg2,…);}//havemorestaticmethodstocreatethefragment,locatethefragmentetc.

}

TherearethreethingsworthmentioningaboutthecodeinListing15-13.BynotoverridingtheonCreateView(),thisfragmentbecomesaheadlessfragment.Becauseafragmentgetsre-createdusingitsdefaultconstructor,wefollowedthenewInstance()patternandalsoextendedthatpatterntouseinit()methodswhichcanbevirtualandinherited.ThislatterapproachisusefulifyouareextendingtheFragmentclassinadeeperhierarchy.

Listing15-14showsastaticmethodonthisAsyncTesterFragmentobjectthatcancreatethisfragment,makeitretainitsstate,andthenregisteritwiththeactivity.

Listing15-14.RegisteringaFragmentasaRetainedFragment

//ThefollowingcodeisinAsyncTesterFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicstaticAsyncTesterFragmentcreateRetainedAsyncTesterFragment(Activityact){AsyncTesterFragmentfrag=AsyncTesterFragment.newInstance();frag.setRetainInstance(true);

FragmentManagerfm=act.getFragmentManager();FragmentTransactionft=fm.beginTransaction();ft.add(frag,AsyncTesterFragment.FRAGMENT_TAG);ft.commit();returnfrag;}

Oncethisretainedfragmentisavailablewiththeactivity,itcanberetrievedanytimeandaskedtostartanAsyncTask.Listing15-15showsthepseudocodefortheAsyncTaskthatisabletointeractwiththisretainedfragmenttocontrolthefragmentdialog

Listing15-15.AnAsyncTaskThatUsesaFragmentDialogThroughaRetainedFragment

//ThefollowingcodeisinMyLongTaskWithFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicclassMyLongTaskWithFragmentextendsAsyncTask<String,Integer,Integer>{//...othercode//ThefollowingreferencepassedinandsetfromtheconstructorAsyncTesterFragmentretainedFragment;

//....othercode@OverrideprotectedvoidonPreExecute(){

....othercode//gettheactivityfromtheretainedfragmentActivityact=retainedFragment.getActivity();//CreatetheprogressdialogProgressDialogFragmentpdf=newProgressDialogFragment();//theshowmethodwilladdandcommitthefragmentdialogpdf.show(act.getFragmentManager(),this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);}@OverrideprotectedvoidonProgressUpdate(){//ifactivityisavailable,getthefragmentdialogfromit,callsetProgress()onit//otherwiseignoretheprogress}@OverrideprotectedvoidonPostExecute(){//ifactivityisinagoodstate//dismissthedialogandtelltherootRADOtodropthepointertotheAsyncTask//ifnotrememberitthroughaflagtocloseitwhenyoucomeback}@Overridepublicvoidattach(){//calledwhentheactivityisback.checktoseeifthistaskisdone//ifsodismissthedialogandremoveyourselffromtheretainedfragment//ifnotcontinuetoupdatetheprogress}@OverrideprotectedIntegerdoInBackground(String…strings){//Dotheactualworkherewhichoccursonaseparatethread}}

ThisAsyncTaskinListing15-15behavesmuchliketheAsyncTaskthatusedtheretainedobject.Oncethistaskknowshowtogetaccesstotheprogressdialogfragmentfromtheretainedfragment,itisprettystraightforwardtosettheprogressonit.Asbefore,thistaskalsoneedstoknowwhentheactivityisreattachedincasethetaskisdonebeforehand.Ifthishappens,theAsyncTaskneedstorememberthisandclosethedialogonreattach.ThepseudocodeinListing15-15satisfiesallthetestconditionssetforthearlier.

Thisconcludesoursecondsolution.Let’sshiftnowtothethirdsolution,wherewewilluseaprogressbarinsteadofaprogressdialogtoshowtheprogressofanAsyncTask.

UsingRetainedFragmentsandProgressBarsAndroidSDKdocumentationonProgressDialog(http://developer.android.com/guide/topics/ui/dialogs.html)isrecommendingthatweuseaProgressBarinanumberofscenariosinsteadasabetterpractice.Thepurportedreasonisthataprogressbarislessintrusive,asitallowsinteractionwithotherareasoftheactivity.Aprogressbar,likeaprogressdialog,canbeindeterminateorfixedinduration.Itcanalsobeacontinuouslyrevolvingcircleorahorizontalbar.YoucanfindthesemodesbylookingupthedocsforProgressBar.Listing15-16givesaquickrundownofasamplingofProgressBarstylesinalayoutfile.

Listing15-16.DifferentWaystoStyleaProgressBarinaLayoutFile

//Thefollowingcodeisinspb_show_progressbars_activity_layout.xml//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanseetheseprogressbarsthroughmenuitem:ShowProgressbars<!--Aregularprogressbar-Alargespinningcircle--><ProgressBarandroid:id="@+id/tpb_progressBar1"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/background_light"/>

<!--Smallspinningcircle--><ProgressBarandroid:id="@+id/tpb_progressBar4"style="?android:attr/progressBarStyleSmall"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/background_light"/>

<!--HorizontalindefiniteProgressbar:aline--><ProgressBarandroid:id="@+id/tpb_progressBar3"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:indeterminate="true"/>

<!--HorizontalfixeddurationProgressbar:aline--><ProgressBarandroid:id="@+id/tpb_progressBar3"style="?android:attr/progressBarStyleHorizontal"

android:layout_width="match_parent"android:layout_height="wrap_content"android:indeterminate="false"android:max="50"android:progress="10"/>

Figure15-3showshowtheprogressbarlayoutsshowninListing15-16lookwhenloadedintoanactivity.Eachtypeofprogressbarislabeledtoindicateitsmodeorbehavior.(UsemenuitemShowProgressBarstoinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip.)

Figure15-3.AsamplingofprogressbarsinAndroid

OutliningtheProgressBarApproachTheapproachtoreporttheprogressofanAsyncTaskthroughaprogressbarissimilartothepreviousapproachthatusedaretainedheadlessfragmentandafragmentprogressdialog.

1. Asintheprevioussolution,havetheactivitycreateaheadlessretainedfragmentthatholdsapointertotheAsyncTask.

2. Embedtheprogressbarintheactivitylayout.AsyncTaskwillgettothisprogressbarthroughtheheadlessretainedfragment.

3. TheAsyncTaskreliesontheheadlessretainedfragmenttobe

informedoftheactivitystatetoaccomplishallofthetestcasesindicatedearlier.

WalkingThroughCorrespondingKeyCodeSnippetsLet’swalkthroughthekeycodesnippetsthatyouwouldneedtomakethissolutionwork.Let’sbeginwiththelocalvariablestheAsyncTaskholdstointeractwiththeretainedfragmentandtheactivity(Listing15-17).

Listing15-17.LocalVariablesofanAsyncTasktoWorkwithaProgressBar

//ThefollowingcodeisinMyLongTaskWithProgressBar.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:TestProgressBarpublicclassMyLongTaskWithProgressBarextendsAsyncTask<String,Integer,Integer>implementsIWorkerObject{publicStringtag=null;//DebugtagprivateMonitoredFragmentretainedFragment;//ReferencetotheretainedfragmentintcurProgress=0;//Totrackcurrentprogress....

Listing15-18showshowtheAsyncTaskinitializestheprogressbarwhenitstarts.

Listing15-18.InitializingaProgressBar

//PartofMyLongTaskWithProgressBar.javaprivatevoidshowProgressBar(){Activityact=retainedFragment.getActivity();ProgressBarpb=(ProgressBar)act.findViewById(R.id.tpb_progressBar1);pb.setProgress(0);pb.setMax(15);pb.setVisibility(View.VISIBLE);}

Listing15-19showshowtheAsyncTasksetstheprogressontheprogressbarafterlocatingit.

Listing15-19.SettingProgressonaProgressBar

//PartofMyLongTaskWithProgressBar.javaprivatevoidsetProgressOnProgressBar(inti){this.curProgress=i;ProgressBarpbar=getProgressBar();if(pbar==null){

Log.d(tag,"Activityisnotavailabletosetprogress");return;}pbar.setProgress(i);}

ThemethodgetProgressBar()thatlocatestheactivityisquitesimple;youjustusethefind()methodtolocatetheProgressBarview.Iftheactivityisnotavailableduetodevicerotation,theProgressBarreferencewillbenullandwewillignoresettingtheprogress.Listing15-20showshowtheAsyncTaskclosestheprogressbar.

Listing15-20.ClosingtheProgressBaronAsyncTaskCompletion

//PartofMyLongTaskWithProgressBar.javaprivatevoidcloseProgressBar(){ProgressBarpbar=getProgressBar();if(pbar==null){Log.d(tag,"Sorryprogressbarisnulltocloseit!");return;}//Dismissthedialogpbar.setVisibility(View.GONE);detachFromParent();}

OncetheProgresBarisremovedfromtheview,thecodeinListing15-20informstheretainedfragmentthatitcanletgooftheAsyncTaskpointershoulditbeholdingit.Dependingonhowtheretainedfragmentholdsthispointer,thisstepmayormaynotbeneeded.Butitisagoodpracticetotelltheparentitnolongerneedstoholdontoareferencethatitdoesn’tneedanymore.So,Listing15-21showshowtheAsyncTaskinformstheparentthatitnolongerneedstoholdapointertotheAsyncTask.

Listing15-21.InformingClients,LiketheRetainedFragment,oftheCompletionofAsyncTask

//TotellthecalledobjectthatI,theAsyncTask,havefinished//TheActivityorretainedfragmentcanactasaclienttothisAsyncTask//AsyncTaskisimaginedtobeaWorkerObjectandhenceunderstandstheIWorkerObjectClient

//MyLongTaskWithProgressBarimplementsIWorkerObject//AsyncTesterFragmentimplementstheIWorkerObjectClient

//CodebelowistakenfromMyLongTaskWithProgressBar.java//ThisimplementstheIWorkerObjectcontract

IWorkerObjectClientclient=null;intworkerObjectPassbackIdentifier=-1;

publicvoidregisterClient(IWorkerObjectClientwoc,intinWorkerObjectPassbackIdentifier){client=woc;this.workerObjectPassbackIdentifier=inWorkerObjectPassbackIdentifier;}privatevoiddetachFromParent(){if(client==null){Log.e(tag,"Youhavefailedtoregisteraclient.");return;}//clientisavailableclient.done(this,workerObjectPassbackIdentifier);}

AddressingKeyDifferenceswiththeProgressBarSolutionTherearesomeunexpecteddifferencesyoumustbeawareofwhenweuseaprogressbarinsteadofaprogressdialog.

Initially,inthelayoutfile,visibilityoftheprogressbarissettoGONEsothatitrepresentsthestatethattheAsyncTaskhasnotevenstarted.OncetheAsyncTaskstartsitwillsetthevisibilitytoVISIBLEandsubsequentlysettheprogressasitgoesalong.However,whentheactivityisre-created,thestatemanagementoftheactivityrequiresthatthecontrolisvisiblecomingoutoftheonCreate()method.BecauseinthelayoutthevisibilityissettobeGONE,theactivitywillnotrestoretheprogressbarstateandyouwillnotseetheprogressbarwhenthedeviceisrotated.Becauseofthis,theAsyncTaskneedstotakeoverthecontrolofthisprogressbarstatemanagementandreinitializeitproperlywhentheactivityisreattached.Listing15-22showshowwedothisintheAsyncTaskcode.

Listing15-22.ManagingtheProgressBarStatefromtheAsyncTask

//TakenfromMyLongTaskWithProgressBar.java//OnactivitystartpublicvoidonStart(Activityact){//dismissdialogifneededif(bDoneFlag==true){Log.d(tag,"OnmystartInoticeIwasdoneearlier");closeProgressBar();return;}Log.d(tag,"Iamreattached.Iamnotdone");setProgressBarRightOnReattach();

}privatevoidsetProgressBarRightOnReattach(){ProgressBarpb=getProgressBar();pb.setMax(15);pb.setProgress(curProgress);pb.setVisibility(View.VISIBLE);}

TheonStart()methodinListing15-22iscalledbytheretainedfragmentontheAsyncTaskwhentheactivityisreattachedtotheretainedfragmentandthefragmentdetectsthattheactivity’sUIisreadytobeused.

Anotherdifferencewhenusingaprogressbaristhebehaviorofthebackbutton.Unlikeaprogressdialog,fortheactivity,youmaywanttoallowthebackbutton.Asthebackbuttoncompletelyremovestheactivity,youmaywanttotakethisopportunitytocancelthetask.ThereleaseResources()methodinListing15-23iscalledbytheretainedfragmentwhenitdetectsthattheactivityisnotgoingtobebackbymonitoringtheisFinishing()flagintheonDestroy()method.

Listing15-23.CancellingtheAsyncTaskonActivityBack

//TakenfromMyLongTaskWithProgressBar.javapublicvoidreleaseResources(){cancel(true);//cancelthetaskdetachFromParent();//removemyself}

AllthreesolutionsoutlinedinthislatterpartofthechapterwillworktocorrectlyshowtheprogressofanAsyncTask.TheSDK-recommendedapproachistousetheProgressBarastherightUIcomponenttodisplaytheprogress.Ourpreferenceforquicktasksthattakejustasecondortwoistousetheprogressbarsaswell.Forataskthattakesalittlelonger—andyoudon’twanttheusertodisturbthestateoftheUI—thenusetheProgressDialoginconjunctionwithaheadlessretainedfragment.Whenyoursolutionsrequireadeephierarchyofobjects,thenuseoftheADOframeworkcouldcomehandyirrespectiveofwhetheryouusethemthroughretainedfragmentsorthroughtheretainedobjects.YoucanseeallofthesolutionsoutlinedherefullyimplementedinthedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip.

TherearefurtherconsiderationsiftheAsyncTaskweredoingupdatesandchangingstate.Ifthatisthecase,youmaywanttouseabackgroundservicesothatitcanberestartediftheprocessistobereclaimedandrestartedlater.Theapproachespresentedhereareadequateforquick-tomedium-levelreadsasyouareexpectingtheusertowait.However,forlonger-timereadsorwrites,youmaywanttoadaptaservice-basedsolution.

References

Thefollowingreferenceswillhelpyoulearnmoreaboutthetopicsdiscussedinthischapter:

http://developer.android.com/reference/android/os/AsyncTask.htmlAkeyresourcethatdefinitivelydocumentsthebehaviorofAsyncTask.

http://www.shanekirk.com/2012/04/asynctask-missteps/:Anotherlookatawell-behavedAsyncTask.

http://www.androidbook.com/item/3536:ResearchnotesonAsyncTaskthatwegatheredinpreparingthischapter.

http://www.androidbook.com/item/3537:AndroidusesJavagenericsofteninitsAPI.ThisURLdocumentsafewbasicsonJavagenericstogetyoustarted.

http://www.androidbook.com/fragments:Asthischapterhasdemonstrated,toworkwithanAsyncTaskauthoritativelyyouneedtoknowalotaboutactivitylifecycle,fragments,theirlifecycle,headlessfragments,configurationchanges,fragmentdialogs,AsyncTask,ADOs,andmore.ThisURLhasanumberofarticlesfocusingonalltheseareas.

http://www.androidbook.com/item/4660:ADOisanabstractionthatoneofourauthorsespousedasahandytooltodealwithconfigurationchange.ThisURLdocumentswhatADOsareandhowtheycouldbeused,anditalsoprovidesapreliminaryimplementation.

http://www.androidbook.com/item/4674:ThisURLdocumentsthebackground,helpfulURLs,codesnippets,andhelpfulhintstoworkwithaProgressBar.

http://www.androidbook.com/item/4680:ThisURLhasagoodbitofresearchonactivitylifecycleintheeventofconfigurationchanges.

http://www.androidbook.com/item/4665:Itisquitehardtowriteprogramsthatworkwellwhendevicesrotate.ThisURLoutlinessomebasictestcasesyoumustsuccessfullyrunforvalidatingAsyncTask.

http://www.androidbook.com/item/4673:ThisURLsuggestsanenhancedpatternforconstructinginheritedfragments.

http://www.androidbook.com/item/4629:Thebestwaytounderstandafragment,includingaretainedfragment,istostudyitscallbacksdiligently.ThisURLprovidesdocumentedsamplecodeforalltheimportantcallbacksofafragment.

http://www.androidbook.com/item/4668:Thebestwaytounderstandanactivitylifecycleisstudyitscallbacksdiligently.ThisURLprovidesdocumentedsamplecodeforalltheimportantactivitycallbacks.

http://www.androidbook.com/item/3634:ThisURLoutlinesourresearchonfragmentdialogs.

http://www.androidbook.com/proandroid5/projects:AlistofdownloadableprojectsfromthisbookisatthisURL.Forthischapter,lookforazipfilenamedProAndroid5_Ch15_TestAsyncTask.zipandalsoProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.Thelatterzipfileistheonethatimplementsthethreeproposedsolutionsforawell-behavedAsyncTask.

SummaryInthischapter,inadditiontocoveringAsyncTask,wehaveintroducedyoutoprogressdialogs,progressbars,headlessretainedfragments,andADOs.Readingthischapter,younotonlyunderstoodAsyncTaskbutalsogottoapplyyourunderstandingofactivitylifecycleandadeepunderstandingoffragments.Wehavealsodocumentedasetofkeytestcasesthatmustbesatisfiedforawell-behavedAndroidapplication.

Chapter16

BroadcastReceiversandLong-RunningServicesAbroadcastreceiverisanothercomponentinanAndroidprocess,alongwithactivities,contentproviders,andservices.Abroadcastreceiverisacomponentthatcanrespondtoabroadcastmessagesentbyaclient.Thismessageismodeledasanintent.Further,abroadcastmessage(intent)canberespondedtobymorethanonereceiver.

AclientcomponentsuchasanactivityoraserviceusesthesendBroadcast(intent)method,availableontheContextclass,tosendabroadcast.ReceivingcomponentsofthebroadcastintentwillneedtoinheritfromaBroadcastReceiverclassavailableintheAndroidSDK.Thesebroadcastreceiversneedtoberegisteredinthemanifestfilethroughareceivercomponenttagtoindicatethatthereceiverisinterestedinrespondingtoacertaintypeofbroadcastintent.

SendingaBroadcastListing16-1showssamplecodethatsendsabroadcastevent.Thiscodecreatesanintentwithauniqueintentactionstring,putsanextrafieldcalledmessageonit,andcallsthesendBroadcast()method.Puttingtheextraontheintentisoptional.

Listing16-1.BroadcastinganIntent

//Thiscodeisinclass:TestBCRActivity.java//Project:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zipprivatevoidtestSendBroadcast(Activityactivity){//CreateanintentwithauniqueactionstringStringuniqueActionString="com.androidbook.intents.testbc";IntentbroadcastIntent=newIntent(uniqueActionString);

//Allowstandalonecross-processesthathavebroadcastreceivers//inthemtobestartedeventhoughtheyareinstoppedstate.broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);

broadcastIntent.putExtra("message","Helloworld");activity.sendBroadcast(broadcastIntent);}

InListing16-1,theactionisanarbitraryidentifierthatsuitsyourneeds.Tomakethisactionstringunique,youmaywanttouseanamespacesimilartoaJavapackage.Also,wewilltalkaboutthecross-processFLAG_INCLUDE_STOPPED_PACKAGESlaterinthischapterinthesectioncalled“Out-of-ProcessReceivers.”

CodingaSimpleReceiverListing16-2showsabroadcastreceiverthatcanrespondtothebroadcastedintentfromListing16-1.

Listing16-2.SampleBroadcastReceiverCode

//ThisclassisinTestBroadcastReceiverprojectinthedownload//Thedownloadforthischapteris:ProAndroid5_Ch16_TestReceivers.zippublicclassTestReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d("TestReceiver","intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}

Creatingabroadcastreceiverisquitesimple.ExtendtheBroadcastReceiverclassandoverridetheonReceive()method.Weareabletoseetheintentinthereceiverandextractthemessagefromit.Nextweneedtoregisterthebroadcastreceiverinthemanifestfileasareceiver.

RegisteringaReceiverintheManifestFileListing16-3showshowyoucandeclareareceiverastherecipientoftheintentwhoseactioniscom.androidbook.intents.testbc.

Listing16-3.AReceiverDefinitionintheManifestFile

<!--Infilename:AndroidManifest.xmlProject:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip--><manifest><application>...

<activity>...</activity>...<receiverandroid:name=".TestReceiver"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver>...</application></manifest>

Thereceiverelementisachildnodeoftheapplicationelementliketheothercomponentnodessuchasanactivity.

Withthereceiver(Listing16-2)anditsregistrationinthemanifestfile(Listing16-3)available,youcaninvokethereceiverusingtheclientcodeinListing16-1.WehaveincludedareferencetothedownloadableZIPfileProAndroid5_Ch16_TestReceivers.zipforthischapterattheendofthischapter.ThisZIPfilehastwoprojects.ThecodereferencedsofarisintheprojectTestBroadcastReceiver.

AccommodatingMultipleReceiversTheideaofabroadcastisthattherecouldbemorethanonereceiver.Let’sreplicateTestReceiver(seeListing16-2)asTestReceiver2andseeifbothcanrespondtothesamebroadcastmessage.ThecodeforTestReceiver2ispresentedinListing16-4.

Listing16-4.SourcecodeforTestReceiver2

//Filename:TestReceiver2.java//Project:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTestReceiver2extendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver2";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}

AddthisreceivertoyourmanifestfileasshowninListing16-5.

Listing16-5.TestReceiver2DefinitionintheManifestFile

<!--Infilename:AndroidManifest.xmlProject:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip--><receiverandroid:name=".TestReceiver2"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver>

Now,ifyoufireofftheeventasinListing16-1,bothreceiverswillbecalled.

WehaveindicatedinChapter13thatthemainthreadrunsallthebroadcastreceiversthatbelongtoasingleprocess.Youcanprovethisbyprintingoutthethreadsignatureineachofthereceivers,includingthemainlineinvokingcode.Youwillseethesamethreadrunningthroughthiscodesequentially.ThesendBroadcast()queuesthebroadcastmessageandletsthemainthreadgetbacktoitsqueue.Responsetothisqueuedmessagebyareceiveriscarriedoutbythesamemainthreadinorder.Whentherearemultiplereceivers,itisnotgooddesigntorelyontheorderofexecutionastowhichreceiverisinvokedfirst.

WorkingwithOut-of-ProcessReceiversTheintentionofabroadcastismorelikelythattheprocessrespondingtoitisanunknownoneandseparatefromtheclientprocess.Youcanprovethisbyreplicatingoneofyourreceiverspresentedsofarandcreatingaseparate.apkfilefromit.ThenwhenyoufireofftheeventfromListing16-1,youwillseethatboththein-processreceivers(thosethatareinthesameprojector.apkfile)andout-of-processreceivers(thosethatareinaseparate.apkfile)areinvoked.YouwillalsoseethroughtheLogCatmessagesthatthein-processandout-of-processreceiversrunintheirrespectivemainthreads.

However,afterAPI12(Android3.1)therearesomewrinklesaroundbroadcastreceiversthatareinexternalprocesses.ThisisduetothelaunchmodeladaptedbytheSDKforsecurityconcerns.Youcanreadaboutthismoreinoneofthereferencelinksprovidedforthischapter.Withthischangeanapplicationwheninstalledwillbeinastoppedstate.Intentsthatcanstartcomponentscannowspecifytotargetthoseapplicationsthatareonlyinstartedstate.Bydefaulttheoldbehaviorpersists.However,forbroadcastintentsthesystemautomaticallyaddsaflagtoexcludeapplicationsthatareinstoppedstate.Toovercomethepreviouspoint,onecanexplicitlysetanintentflagonthebroadcastintenttoincludethosestoppedapplicationsasvalidtargets.ThisiswhatyouseeincodeListing16-1.

Wehaveincludedanadditionalseparatestand-aloneprojectcalledStandaloneBroadcastReceiverinthechapter’sdownloadableZIPfileProAndroid5_Ch16_TestReceivers.ziptotestthisconcept.Totryit,youhave

todeployboththeinvokingprojectTestBroadcastReceiverandthestand-alonereceiver’sprojectStandloneBroadcastReceiverontheemulator.YoucanthenusetheTestBroadcastReceiverprojecttosendthebroadcasteventandmonitortheLogCatforthereceiversrespondingfromtheStandaloneBroadcastReceiver.

UsingNotificationsfromaReceiverBroadcastreceiversoftenneedtocommunicatetotheuseraboutsomethingthathappenedorasastatus.Thisisusuallydonebyalertingtheuserthroughanotificationiconinthesystem-widenotificationbar.Wewillnowshowyouhowtocreateanotificationfromabroadcastreceiver,sendit,andviewitthroughthenotificationmanager.

MonitoringNotificationsThroughtheNotificationManagerAndroidshowsiconsofnotificationsasalertsinthenotificationarea.ThenotificationareaislocatedatthetopofdeviceinastripthatlookslikeFigure16-1.ThelookandplacementofthenotificationareamaychangebasedonwhetherthedeviceisatabletoraphoneandmayattimesalsochangebasedonAndroidrelease.

Figure16-1.Androidnotificationiconstatusbar

ThenotificationareashowninFigure16-1iscalledthestatusbar.Itcontainssystemindicatorssuchasbatterystrength,signalstrength,andsoon.Whenwedeliveranotification,thenotificationwillappearasaniconintheareashowninFigure16-1.ThenotificationiconisillustratedinFigure16-2.

Figure16-2.Statusbarshowinganotificationicon

Thenotificationiconisanindicatortotheuserthatsomethingneedstobeobserved.Toseethefullnotification,youhavetoholdafingerontheiconanddragthetitlestripshowninFigure16-2downlikeacurtain.Thiswillexpandthenotificationarea,asshowninFigure16-3.

Figure16-3.Expandednotificationview

IntheexpandedviewofthenotificationinFigure16-3,yougettoseethedetailssuppliedtothenotification.Youcanclickthenotificationdetailtofireofftheintenttobringupthefullapplicationtowhichthenotificationbelongs.Youcanusethisviewtoclearnotifications.Alsodependingonthedeviceandreleasetheremaybealternatewaysofopeningthenotifications.Let’sseenowhowtogenerateanotificationiconliketheoneshowninFigures16-2and16-3.

SendingaNotificationWhenyoucreateanotificationobject,itneedstohavethefollowingelements:

Anicontodisplay

Tickertextlike“helloworld”

Thetimewhenitisdelivered

Onceyouhaveanotificationobjectconstructed,yougetthenotificationmanagerreferencebyaskingthecontextforasystemservicenamedContext.NOTIFICATION_SERVICE.Thenyouaskthenotificationmanagertosendthenotification.Listing16-6hasthesourcecodeforabroadcastreceiverthatsendsthenotificationshowninFigures16-2and16-2.

Listing16-6.AReceiverThatSendsaNotification

//Filename:NotificationReceiver.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassNotificationReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="NotificationReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){

Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);this.sendNotification(context,message);}privatevoidsendNotification(Contextctx,Stringmessage){//Getthenotificationmanager

Stringns=Context.NOTIFICATION_SERVICE;NotificationManagernm=(NotificationManager)ctx.getSystemService(ns);//PrepareNotificationObjectDetails

inticon=R.drawable.robot;CharSequencetickerText="Hello";longwhen=System.currentTimeMillis();//Gettheintenttofirewhenthenotificationisselected

Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.google.com"));PendingIntentpi=PendingIntent.getActivity(ctx,0,intent,0);//Createthenotificationobjectthroughthebuilder

Notificationnotification=newNotification.Builder(ctx).setContentTitle("title").setContentText(tickerText).setSmallIcon(icon).setWhen(when).setContentIntent(pi)

.setContentInfo("AddtionalInformation:ContentInfo").build();//Sendnotification

//Thefirstargumentisauniqueidforthisnotification.//Thisidallowsyoutocancelthenotificationlater//Thisidalsoallowsyoutoupdateyournotification//bycreatinganewnotificationandresendingitagainstthatid//Thisidisuniquewithinthisapplicationnm.notify(1,notification);}}

Thecontentviewofanotificationisdisplayedwhenthenotificationisexpanded.ThisiswhatyouseeinFigure16-2.ThecontentviewneedstobeaRemoteViewsobject.However,wedon’tpassacontentviewdirectly.BasedontheparameterspassedtotheBuilderobject,theBuilderobjectcreatesanappropriateRemoteViewsobjectandsetsitonthenotification.TheBuilderinterfacealsohasamethodtodirectlysetthecontentviewasawholeifneeded.

Thestepsfordirectlyusingremoteviewsforacontentviewofanotificationareasfollows:

1. Createalayoutfile.

2. CreateaRemoteViewsobjectusingthepackagenameandthelayoutfileID.

3. CallsetContent()ontheNotification.Builderobjectbeforecallingthebuild()methodtocreatethenotificationobject,whichisthensenttothenotificationmanager.

Keepinmindthatonlyalimitedsetofcontrolsmayparticipateinaremoteview,suchasFrameLayout,LinearLayout,RelativeLayout,AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView.

ThecodeinListing16-6createsanotificationusingtheBuilderobjecttosettheimplicitcontentview(throughtitleandtext)andtheintenttofire(inourcase,thisintentisthebrowserintent).AnewnotificationcanbecreatedtoberesentthroughthenotificationmanagerinordertoupdatethepreviousinstanceofitusingtheuniqueIDofthenotification.TheIDofthenotification,whichissetto1inListing16-6,isuniquewithinthisapplicationcontext.Thisuniquenessallowsustocontinuouslyupdatewhatishappeningtothatnotificationandalsocancelitifneeded.

Youmayalsowanttolookatthevariousflagsavailablewhilecreatinganotification,suchasFLAG_NO_CLEARandFLAG_ONGOING_EVENT,tocontrolthepersistenceofthesenotifications.YoucanusethefollowingURLtochecktheseflags:

http://developer.android.com/reference/android/app/Notification.html

StartinganActivityinaBroadcastReceiverAlthoughyou’readvisedtousethenotificationmanagerwhenauserneedstobeinformed,Androiddoesallowyoutospawnanactivityexplicitly.YoucandothisbyusingtheusualstartActivity()methodbutwiththefollowingflagsaddedtotheintentthatisusedastheargumenttothestartActivity():

Intent.FLAG_ACTIVITY_NEW_TASK

Intent.FLAG_FROM_BACKGROUND

Intent.FLAG_ACTIVITY_SINGLE_TOP

ExploringLong-RunningReceiversandServicesSofar,wehavecoveredthehappypathofbroadcastreceiverswheretheexecutionofabroadcastreceiverisunlikelytotakemorethan10seconds.Theproblemspaceisabitcomplicatedifwewanttoperformtasksthattakelongerthan10seconds.

Tounderstandwhy,let’sreviewafewfactsaboutbroadcastreceivers:

Abroadcastreceiver,likeothercomponentsofanAndroidprocess,runsonthemainthread.HenceholdingupthecodeinabroadcastreceiverwillholdupthemainthreadandwillresultinANR.Thetimelimitonabroadcastreceiveris10secondscomparedto5secondsforanactivity.Itisabitofareprieve,butnotverymuch.

Theprocesshostingthebroadcastreceiverwillstartandterminatealongwiththebroadcastreceiverexecution.Hencetheprocesswillnotstickaroundafterthebroadcastreceiver’sonReceive()methodreturns.Ofcourse,thisisassumingthattheprocesscontainsonlythebroadcastreceiver.Iftheprocesscontainsothercomponents,suchasactivitiesorservices,thatarealreadyrunning,thenthelifetimeoftheprocesstakesthesecomponentlifecyclesintoaccountaswell.

Unlikeaserviceprocess,abroadcastreceiverprocesswillnotgetrestarted.

Ifabroadcastreceiverweretostartaseparatethreadandreturntothemainthread,Androidwillassumethattheworkiscompleteandwillshutdowntheprocesseveniftherearethreadsrunning,bringingthosethreadstoanabruptstop.

Androidautomaticallyacquiresapartialwakelockwheninvokinga

broadcastserviceandreleasesitwhenitreturnsfromtheserviceinthemainthread.AwakelockisamechanismandanAPIclassavailableintheSDKtokeepthedevicefromgoingtosleeportowakeitupifitisalreadyasleep.

Giventhesepredicates,howcanweexecutelonger-runningcodeinresponsetoabroadcastevent?

UnderstandingLong-RunningBroadcastReceiverProtocolTheanswerliesinresolvingthefollowing:

WewillclearlyneedaseparatethreadsothatthemainthreadcangetbackandavoidANRmessages.

TostopAndroidfromkillingtheprocessandhencetheworkerthread,weneedtotellAndroidthatthisprocesscontainsacomponent,suchasaservice,withalifecycle.Soweneedtocreateorstartthatservice.Theserviceitselfcannotdirectlydotheworkformorethan5secondsbecausethathappensonthemainthread,sotheserviceneedstostartaworkerthreadandletthemainthreadgo.

Forthedurationoftheworkerthread’sexecution,weneedtoholdontothepartialwakelocksothatthedevicewon’tgotosleep.Apartialwakelockwillallowthedevicetoruncodewithoutturningonthescreenandsoon,whichallowsforlongerbatterylife.

Thepartialwakelockmustbeobtainedinthemainlinecodeofthereceiver;otherwise,itwillbetoolate.Forexample,youcannotdothisintheservice,becauseitmaybetoolatebetweenthestartService()beingissuedbythebroadcastreceiverandtheonStartCommand()ofaservicethatbeginsexecution.

Becausewearecreatingaservice,theserviceitselfcanbebroughtdownandbroughtbackupbecauseoflow-memoryconditions.Ifthishappens,weneedtoacquirethewakelockagain.

WhentheworkerthreadstartedbytheonStartCommand()methodoftheservicecompletesitswork,itneedstotelltheservicetostopsothatitcanbeputtobedandnotbroughtbacktolifebyAndroid.

Itisalsopossiblethatmorethanonebroadcasteventcanoccur.Giventhat,weneedtobecautiousabouthowmanyworkerthreadsweneedtospawn.

Giventhesefacts,therecommendedprotocolforextendingthelifeofabroadcastreceiverisasfollows:

1. Geta(static)partialwakelockintheonReceive()methodofthebroadcastreceiver.Thepartialwakelockneedstobestatictoallowcommunicationbetweenthebroadcastreceiverandtheservice.Thereisnootherwayofpassingareferenceofthewakelocktotheservice,astheserviceisinvokedthroughadefaultconstructorthattakesnoparameters.

2. Startalocalservicesothattheprocesswon’tbekilled.

3. Intheservice,startaworkerthreadtodothework.DonotdotheworkintheonStart()methodoftheservice.Ifyoudo,youarebasicallyholdingupthemainthreadagain.

4. Whentheworkerthreadisdone,telltheservicetostopitselfeitherdirectlyorthroughahandler.

5. Havetheserviceturnoffthestaticwakelock.

UnderstandingIntentServiceRecognizingtheneedforaservicetonotholdupthemainthread,AndroidhasprovidedautilitylocalserviceimplementationcalledIntentServicetooffloadworktoaworkerthreadsothatthemainthreadcanbereleasedafterschedulingtheworktothesubthread.Underthisscheme,whenyoucallstartService()onanIntentService,theIntentServicewillqueuethatrequesttoasubthreadusingalooperandahandlersothataderivedmethodoftheIntentServiceiscalledtodotheactualworkonasingleworkerthread.

HereiswhattheAPIdocumentationforIntentServicesays:

IntentService is a base class for Services that handle asynchronous requests(expressed as Intents) on demand. Clients send requests throughstartService(Intent)calls;theserviceisstartedasneeded,handleseachIntentinturnusingaworkerthread,andstopsitselfwhenitrunsoutofwork.This“workqueue processor” pattern is commonly used to offload tasks from anapplication'smainthread.TheIntentServiceclassexiststosimplifythispatternand takecareof themechanics.Touse it, extend IntentServiceand implementonHandleIntent(Intent). IntentServicewill receive the Intents, launchaworkerthread,andstoptheserviceasappropriate.Allrequestsarehandledonasingleworker thread—they may take as long as necessary (and will not block theapplication'smainloop),butonlyonerequestwillbeprocessedatatime.

ThisideaisdemonstratedusingasimpleexampleinListing16-7.YouextendtheIntentServiceandprovidewhatyouwanttodointheonHandleIntent()method.

Listing16-7.UsingIntentService

//YoucanseefileTest30SecBCRService.javaforexample//Project:StandaloneBroadcastReceiver,Download:

ProAndroid5_Ch16_TestReceivers.zippublicclassMyServiceextendsIntentService{publicMyService(){super("some-java-package-like-name-used-for-debugging");}protectedvoidonHandleIntent(Intentintent){//logthreadsignatureifyouwanttoseethatitisrunningonaseparatethread//Ex:Utils.logThreadSignature("MyService");//dotheworkinthissubthread//andreturn}}

Onceyouhaveaservicelikethis,youcanregisterthisserviceinthemanifestfileanduseclientcodetoinvokethisserviceascontext.startService(newIntent(context,MyService.class)).ThiswillresultinacalltoonHandleIntent()inListing16-7.YouwillnoticethatifyouweretousethecommentedoutmethodUtils.logThreadSignature()inListing16-7inyouractualcode,itwillprinttheIDoftheworkerthreadandnotthemainthread.YoucanseetheUtilsclassintheprojectanddownloadreferenceslistedinthecommentssectionofListing16-7.

ExtendingIntentServiceforaBroadcastReceiverFromtheperspectiveofabroadcastreceiver,anIntentServiceisawonderfulthing.Itletsusexecutelong-runningcodewithoutblockingthemainthread.Notonlythat,beingaservice,anIntentServiceprovidesaprocessthatkeepsrunningwhenthebroadcastcodereturns.SocanweusetheIntentServicefortheneedsofalong-runningoperation?Yesandno.

Yes,becausetheIntentServicedoestwothings:First,itkeepstheprocessrunningbecauseitisaservice.Second,itletsthemainthreadgoandavoidsrelatedANRmessages.

Tounderstandthe“no”answer,youneedtounderstandwakelocksabitmore.Whenabroadcastreceiverisinvokedthroughanalarmmanager,thedevicemaynotbeon.Sothealarmmanagerpartiallyturnsonthedevice(justenoughtorunthecodewithoutanyUI)bymakingacalltothepowermanagerandrequestingawakelock.Thewakelockgetsreleasedassoonasthebroadcastreceiverreturns.

ThisleavestheIntentServiceinvocationwithoutawakelock,sothedevicemaygotosleepbeforetheactualcoderuns.However,IntentService,beingageneral-purposeextensiontoaservice,itdoesnotacquireawakelock.Soweneedfurtherprops

ontopofanIntentService.Weneedanabstraction.

MarkMurphyhascreatedavariantoftheIntentServicecalledWakefulIntentServicethatkeepsthesemanticsofusinganIntentServicebutalsoacquiresthewakelockandreleasesitproperlyunderavarietyofconditions.Youcanlookathisimplementationathttp://github.com/commonsguy/cwac-wakeful.

ExploringLong-RunningBroadcastServiceAbstractionWakefulIntentServiceisagoodabstraction.However,wewanttogoastepfurthersothatourabstractionparallelsthemethodofextendingIntentServiceasinListing16-7anddoeseverythingthatanIntentServicedoesbutalsoprovidesfewmorebenefits:

PasstheoriginalintentthatwaspassedtothebroadcastreceivertotheoverriddenmethodonHandleIntent.Thisallowsustolargelyhidethebroadcastreceiver,simulatingaprogrammingexperiencethataserviceisstartedinresponsetoabroadcastmessage.Thisisreallythegoalforthisabstractionwhilesomeextrasarethrownin.

Acquireandreleasewakelocks(similartoWakefulIntentService).

Dealwithaservicebeingrestarted.

Allowauniformwaytodealwiththewakelockformultiplereceiversandmultipleservicesinthesameprocess.

WewillcallthisabstractclassALongRunningNonStickyBroadcastService.Asthenamesuggests,wewantthisservicetoallowforlong-runningwork.Itwillalsobespecificallybuiltforabroadcastreceiver.Thisservicewillalsobenonsticky(wewillexplainthisconceptlaterinthechapter,butbriefly,thisindicatesthatAndroidwillnotstarttheserviceiftherearenomessagesinthequeue).ToallowforthebehaviorofanIntentService,itwillextendtheIntentServiceandoverridetheonHandleIntentmethod.

Combiningtheseideas,theabstractALongRunningNonStickyBroadcastServiceservicewillhaveasignaturethatlookslikeListing16-8.

Listing16-8.Long-RunningServiceAbstractIdea

publicabstractclassALongRunningNonStickyBroadcastServiceextendsIntentService{//...otherimplementationdetails

//thefollowingmethodwillbecalledbytheonHandleIntentofIntentService//thisiswheretheactualworkhappensinthisderivedabstractclassprotectedabstractvoidhandleBroadcastIntent(IntentbroadcastIntent);

//...otherimplementationdetails

}

TheimplementationdetailsforthisALongRunningNonStickyBroadcastServiceareatouchinvolved,andwewillcoverthemsoonafterweexplainwhywearegoingafterthistypeofservice.Wewanttodemonstratefirsttheutilityandsimplicityofhavingit.

OncewehavethisabstractclassofListing16-8,theMyServiceexampleinListing16-7canberewrittenasinListing16-9.

Listing16-9.Long-RunningServiceSampleUsage

publicclassMyServiceextendsALongRunningNonStickyBroadcastService{//..otherimplementationdetailsprotectedvoidhandleBroadcastIntent(IntentbroadcastIntent){//Youcanusethefollowingmethodtoseewhichthreadrunsthiscode//Utils.logThreadSignature("MyService");//dotheworkhere//andreturn}//..otherimplementationdetails}

ThesimplicityofListing16-9isthatthiscodeisinvokedassoonasaclientfiresoffabroadcastintent.Especiallythefactthatyouarereceivingdirectly,unmodified,thesameintentthatinvokedthebroadcastreceiver.Itisasifthebroadcastreceiverhasdisappearedfromthesolution.

Asyoucansee,youcanextendthisnewlong-runningserviceclass(justlikeIntentServiceandWakefulIntentService)andoverrideasinglemethodanddoverylittletonothinginthebroadcastreceiver.Yourworkwillbedoneinaworkerthread(thankstoIntentService)withoutblockingthemainthread.

Listing16-9isasimpleexampledemonstratingtheconcept.Let’sturntoamorecompleteimplementationthatimplementsalong-runningservicethatcanrunfor60secondsinresponsetoabroadcastevent(provingthatwecanrunformorethan10secondsandavoidanANRmessage).WewillcallthisserviceappropriatelyTest60SecBCRService(BCRstandsforbroadcastreceiver),anditsimplementationisshowninListing16-10.

Listing16-10.SourcecodeforTest60SecBCRService

//Filename:Test30SecBCRService.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTest60SecBCRServiceextendsALongRunningNonStickyBroadcastService{publicstaticStringtag="Test60SecBCRService";//RequiredbyIntentServicetopasstheclassnamefordebugneedspublicTest60SecBCRService(){super("com.androidbook.service.Test60SecBCRService");}/*Performlong-runningoperationsinthismethod.*Thisisexecutedinaseparatethread.*/@OverrideprotectedvoidhandleBroadcastIntent(IntentbroadcastIntent){//UtilsclassisinthedownloadprojectmentionedUtils.logThreadSignature(tag);Log.d(tag,"Sleepingfor60secs");//Usethethreadtosleepfor60secondsUtils.sleepForInSecs(60);Stringmessage=broadcastIntent.getStringExtra("message");Log.d(tag,"Jobcompleted");Log.d(tag,message);}}

Asyoucansee,thiscodesuccessfullysimulatesdoingworkfor60secondsandstillavoidstheANRmessage.TheutilitymethodsinListing16-10areself-explanatoryandavailableinthedownloadprojectsforthischapter.TheprojectnameanddownloadfilenameareinthecommentssectionofthecodeinListing16-10.

DesigningALong-RunningReceiverOncewehavethelong-runningserviceinListing16-10,weneedtobeabletoinvoketheservicefromabroadcastreceiver.Againwearegoingafteranabstractiontohidethebroadcastreceiverasmuchaspossible.

Thefirstgoalofalong-runningbroadcastreceiveristodelegatetheworktothelong-runningservice.Todothis,thelong-runningreceiverwillneedtheclassnameofthelong-runningservicetoinvokeit.Thesecondgoalistoacquireawakelock.Thethirdgoalistotransfertheoriginalintentthatthebroadcastreceiverisinvokedontotheservice.WewilldothisbystickingtheoriginalintentasaParcelableintheintentextras.Wewilluseoriginal_intentasthenameforthisextra.Thelong-runningservicethenextractsoriginal_intentandpassesittotheoverriddenmethodofthelong-runningservice

(youwillseethislaterintheimplementationofthelong-runningservice).Thisfacilitythusgivestheimpressionthatthelong-runningserviceisindeedanextensionofthebroadcastreceiver.

Letusabstractoutthesethreethingsandprovideabaseclass.Theonlybitofinformationthislong-runningreceiverabstractionneedsisthenameofthelong-runningserviceclass(LRSClass)throughanabstractmethodcalledgetLRSClass().

Puttingtheseneedstogether,sourcecodefortheimplementationoftheabstractclassALongRunningReceiverisinListing16-11.

Listing16-11.ALongRunningReceiverAbstraction

//Filename:ALongRunningReceiver.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicabstractclassALongRunningReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="ALongRunningReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"Receiverstarted");//LightedGreenRoomabstractstheAndroidWakeLock//tokeepthedevicepartiallyon.//Inshortthisisequivalenttoturningon//oracquiringthewakelock.LightedGreenRoom.setup(context);startService(context,intent);Log.d(tag,"Receiverfinished");}privatevoidstartService(Contextcontext,Intentintent){IntentserviceIntent=newIntent(context,getLRSClass());serviceIntent.putExtra("original_intent",intent);context.startService(serviceIntent);}/**Overridethismethodtoreturnthe*"class"objectbelongingtothe*nonstickyserviceclass.*/publicabstractClassgetLRSClass();}

Intheprecedingbroadcastreceivercode,youseereferencestoaclasscalledLightedGreenRoom.Thisisawrapperaroundastaticwakelock.Inadditiontobeingawakelock,thisclasstriestocatertoworkingwithmultiplereceivers,multipleservices,

etc.,sothatallwaki-nessisproperlycoordinated.Forthepurposeofunderstanding,youcantreatitasifitisastaticwakelock.ThisabstractioniscalledaLightedGreenRoombecauseitisaimedatsavingpowerforthedevicelikethevarious“green”movements.Furthermoreitiscalled“Lighted”becauseitstartsoffbeing“lighted”firstasthebroadcastreceiverturnsitonassoonasitiskickedoff.Thelastservicetouseitwillturnitoff.

Oncethereceiverabstractionisavailable,you’llneedareceiverthatworkshandinhandwiththe60-secondlong-runningserviceinListing16-11.SuchareceiverisprovidedinListing16-12.

Listing16-12.ASampleLong-RunningBroadcastReceiver,Test60SecBCR

//Filename:Test60SecBCR.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTest60SecBCRextendsALongRunningReceiver{@OverridepublicClassgetLRSClass(){Utils.logThreadSignature("Test60SecBCR");returnTest60SecBCRService.class;}}

JustliketheserviceabstractioninListings16-10and16-11,thecodeinListing16-12usesanabstractionforthebroadcastreceiver.ThereceiverabstractionstartstheserviceindicatedbytheserviceclassreturnedbythegetLRSClass()method.

Thusfar,wehavedemonstratedwhyweneededthetwoimportantabstractionstoimplementlong-runningservicesinvokedbybroadcastreceivers:

ALongRunningNonStickyBroadcastService(Listing16-8)

ALongRunningReceiver(Listing16-11)

AbstractingaWakeLockwithLightedGreenRoomAsmentionedearlier,theprimarypurposeoftheLightedGreenRoomabstractionistosimplifytheinteractionwiththewakelock,andawakelockisusedtokeepthedeviceonduringbackgroundprocessing.Youreallydon’tneedthedetailsoftheimplementationoftheLightedGreenRoom,butmerelyitsinterfaceandthecallsthataremadeagainstit.JustkeepinmindthatitisathinwrapperaroundtheAndroidSDKwakelock.Initssimplestimplementation,itcanjustbeassimpleasturningthewakelockon(acquire)andoff(release).Listing16-13showshowawakelockisusedtypicallyasstatedintheSDK.

Listing16-13.PsuedocodeforworkingwiththeWakeLockAPI

//Getaccesstothepowermanagerservice

PowerManagerpm=

(PowerManager)inCtx.getSystemService(Context.POWER_SERVICE);

//GetholdofawakelockPowerManager.WakeLockwl=pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,tag);

//Acquirethewakelockwl.acquire();

//dosomework//whilethisworkisbeingdonethedevicewillbeonpartially

//releasetheWakelockwl.release();

Giventhisinteraction,thebroadcastreceiverissupposedacquirethelock,andwhenthelong-runningserviceisfinished,itneedstoreleasethelock.Assaidearlierthereisnogoodwaytopassthewakelockvariabletotheservicefromthebroadcastreceiver.Theonlywaytheserviceknowsaboutthiswakelockistouseastaticorapplication-levelvariable.

Anotherdifficultyinacquiringandreleasingawakelockisthereferencecount.Asabroadcastreceiverisinvokedmultipletimes,iftheinvocationsoverlap,therearegoingtobemultiplecallstoacquirethewakelock.Similarly,therearegoingtobemultiplecallstorelease.Ifthenumberofacquireandreleasecallsdon’tmatch,wewillendupwithawakelockthatatworstkeepsthedeviceonforfarlongerthanneeded.Also,whentheserviceisnolongerneededandthegarbagecollectionruns,ifthewakelockcountsaremismatched,therewillbearuntimeexceptionintheLogCat.TheseissueshavepromptedustodoourbesttoabstractthewakelockasaLightedGreenRoomtoensureproperusage.Therewillbeoneoftheseobjectsperprocessthatkeepsawakelockandensuresitisturnedonandoffproperly.Theincludedprojecthasanimplementationforthisclass.Ifyoufindthatcodetoocomplicatedduetothenumberofconditionsittakesintoaccount,youcanjuststartwithasimplestaticvariableandturnitonandoffastheservicestartsandclosesandrefineittosuityourparticularconditions.

Areasonableapproachforthebroadcastreceiverandtheservicetocommunicatewitheachotheristhroughastaticvariable.InsteadofmakingWakeLockstatic,wehavemadetheentireLightedGreenRoomastaticinstance.However,everyothervariableinsideLightedGreenRoomstayslocalandnonstatic.

EverypublicmethodofLightedGreenRoomisalsoexposedasastaticmethodforconvenience.Wehaveusedtheconventionofnamingthesemethodsstartingwith“s_”.Youcanchoose,instead,togetridofthestaticmethodsanddirectlycallthesingleobjectinstanceofLightedGreenRoom.

ImplementingaLong-RunningServiceTopresentthelong-runningserviceabstraction,wehavetotakeonemoredetourtoexplainthelifetimeofaserviceandhowitrelatestotheimplementationofonStartCommand.Thisisthemethodthatisultimatelyresponsibleforstartingtheworkerthreadandthesemanticsofaservice.

WhenaserviceisstartedthroughstartService,theservicegetscreatedfirst,anditsonStartCommandmethodiscalled.Androidhasprovisionstokeepthisprocessinmemorysothattheservicecanbecompletedevenwhenservingmultipleincomingclientrequests.However,underdemandingmemoryconditions,AndroidmaychoosetoreclaimtheprocessandcalltheonDestroy()methodoftheservice.

NoteAndroidtriestocalltheonDestroy()methodforaservicetoreclaimitsresourceswhentheserviceisnotexecutingitsonCreate(),onStart(),oronDestroy()method,orinotherwordswhentheserviceisidle.

However,unlikeanactivitythatisshutdown,aserviceisscheduledtorestartagainwhenresourcesareavailableiftherearependingstartServiceintentsinthequeue.TheservicewillbewokenupandthenextintentdeliveredtoitviaonStartCommand().Ofcourse,onCreate()willbecalledwhentheserviceisbroughtback.

Becauseservicesareautomaticallyrestartediftheyarenotexplicitlystopped,itisreasonabletothinkthat,unlikeactivitiesandothercomponents,aservicecomponentisfundamentallyastickycomponent.

UnderstandingaNonstickyServiceAservicewillnotbeautomaticallyrestartedifaclientexplicitlycallsstopService.Dependingonhowmanyclientsarestillconnected,thisstopServicemethodcanmovetheserviceintoastoppedstate,atwhichtimetheservice’sonDestroymethodiscalledandtheservicelifecycleiscomplete.Onceaservicehasbeenstoppedlikethisbyitslastclient,theservicewillnotbebroughtback.

Thisprotocolworkswellwheneverythinghappensasperdesign,wherestartandstopmethodsarecalledandexecutedinsequenceandwithoutamiss.PriortoAndroid2.0,deviceshaveseenalotofserviceshangingaroundandclaimingresourceseventhoughtherewasnoworktobedone,meaningAndroidbroughttheservicesbackintomemoryeventhoughtherewerenomessagesinthequeue.ThiswouldhavehappenedwhenstopServicewasnotinvokedeitherbecauseofanexceptionorbecausetheprocesswastakenoutbetweenonStartCommandandstopService.

Android2.0introducedasolutionsothatwecanindicatetothesystem,iftherearenopendingintents,thatitshouldn’tbotherrestartingtheservice.Thisisdonebyreturningthenonstickyflag(Service.START_NOT_STICKY)fromonStartCommand.

However,nonstickyisnotreallythatnonsticky.Evenifwemarktheserviceasnonsticky,

iftherearependingintents,Androidwillbringtheservicebacktolife.Thissettingappliesonlywhentherearenopendingintents.

UnderstandingaStickyServiceWhatdoesitmeanforaservicetobereallystickythen?Thestickyflag(Service.START_STICKY)meansthatAndroidshouldrestarttheserviceeveniftherearenopendingintents.Whentheserviceisrestarted,callonCreateandonStartCommandwithanullintent.Thiswillgivetheserviceanopportunity,ifneedbe,tocallstopSelfifthatisappropriate.Theimplicationisthataservicethatisstickyneedstodealwithnullintentsonrestarts.

UnderstandingRedeliverIntentsOptionLocalservicesinparticularfollowapatternwhereonStartandstopSelfarecalledinpairs.AclientcallsonStart.Theservice,whenitfinishesthatwork,callsstopSelf.Ifaservicetakes,say,30minutestocompleteatask,itwillnotcallstopSelffor30minutes.Meanwhile,theserviceisreclaimedduetolow-memoryconditionsandhigher-priorityjobs.Ifweusethenonstickyflag,theservicewillnotwakeup,andwewouldneverhavecalledstopSelf.

Manytimes,thisisOK.However,ifyouwanttomakesurewhetherthesetwocallshappenforsure,youcantellAndroidnottounqueuethestarteventuntilstopSelfiscalled.Thisensuresthatwhentheserviceisreclaimed,thereisalwaysapendingeventunlessthestopSelfiscalled.Thisiscalledredelivermode,anditcanbeindicatedinreplytotheonStartCommandmethodbyreturningtheService.START_REDELIVER_INTENTflag.

CodingaLong-RunningServiceNowthatyouhavethebackgroundonIntentService,service-startflags,andthelightedgreenroom,we’rereadytotakealookatthelong-runningserviceinListing16-14.

Listing16-14.ALong-RunningServiceAbstraction

//Filename:ALongRunningNonStickyBroadcastService.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicabstractclassALongRunningNonStickyBroadcastServiceextendsIntentService{publicstaticStringtag="ALongRunningBroadcastService";//Thisiswhatyouoverridetodoyourwork

protectedabstractvoidhandleBroadcastIntent(IntentbroadcastIntent);

publicALongRunningNonStickyBroadcastService(Stringname){super(name);}/**Thismethodcanbeinvokedundertwocircumstances*1.Whenabroadcastreceiverissuesa"startService"*2.whenandroidrestartsthisserviceduetopending"startService"intents.**Incase1,thebroadcastreceiverhasalready*setupthe"lightedgreenroom"andtherebygottenthewakelock**Incase2,weneedtodothesame.*/@OverridepublicvoidonCreate(){super.onCreate();

//Setupthegreenroom//Thesetupiscapableofgettingcalledmultipletimes.LightedGreenRoom.setup(this.getApplicationContext());

//Itispossiblethatmorethanoneserviceofthistypeisrunning.//Knowingthenumberwillallowustocleanupthewakelocksinondestroy.LightedGreenRoom.s_registerClient();}@OverridepublicintonStartCommand(Intentintent,intflag,intstartId){//CalltheIntentService"onstart"super.onStart(intent,startId);

//TellthegreenroomthereisavisitorLightedGreenRoom.s_enter();

//markthisasnonsticky//Means:Don'trestarttheserviceifthereareno//pendingintents.returnService.START_NOT_STICKY;}/*

*NotethatthismethodcallrunsinasecondarythreadsetupbytheIntentService.**OverridethismethodfromIntentService.*Retrievetheoriginalbroadcastintent.*Callthederivedclasstohandlethebroadcastintent.*finallytellthelightedroomthatyouareleaving.*ifthisisthelastvisitorthenthelock*willbereleased.*/@OverridefinalprotectedvoidonHandleIntent(Intentintent){try{IntentbroadcastIntent=intent.getParcelableExtra("original_intent");handleBroadcastIntent(broadcastIntent);}finally{//releasethewakelockifyouarethelastoneLightedGreenRoom.s_leave();}}/*IfAndroidreclaimsthisprocess,thismethodwillreleasethelock*irrespectiveofhowmanyvisitorsthereare.*/@OverridepublicvoidonDestroy(){super.onDestroy();//Doanycleanup,ifneeded,whenaservicenolongerneedsawakelockLightedGreenRoom.s_unRegisterClient();}}

ThisclassextendsIntentServiceandgetsallthebenefitsofaworkerthreadassetupbyIntentService.Inaddition,itspecializestheIntentServicefurthersothatitissetupasanonstickyservice.Fromadeveloper’sperspective,theprimarymethodtofocusonistheabstracthandleBroadcastIntent()method.Listing16-15showsyouhowtosetupthereceiverandthecorrespondingserviceinthemanifestfile.

Listing16-15.TheLong-RunningReceiverandServiceDefinition

<!--Infilename:AndroidManifest.xmlProject:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip

--><manifest…>......<application….><receiverandroid:name=".Test60SecBCR"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver><serviceandroid:name=".Test60SecBCRService"/></application>.....<uses-permissionandroid:name="android.permission.WAKE_LOCK"/></manifest>

Noticethatyouwillneedthewakelockpermissiontorunthislong-runningreceiverabstraction.Completesourcecodeforallofthereceiversandlong-runningservicesisavailableinthedownloadableprojectsforthischapter.Listing16-15bringsouttheessenceofthelong-runningservicesinvokedbyabroadcastreceiver.ThisabstractionstatesthatyouwriteacoupleoflinestocreateareceiverliketheTest60SecBCR(Listing16-12),andthenwriteajavamethodsimilartotheoneincodeTest60SecBCRService(Listing16-10).Giventhereceiverandthejavamethodthatyouwanttorunforalongtime,youcanexecutethatmethodinresponsetothebroadcastevent.ThisabstractionensuresthatthemethodthencanrunaslongasittakeswithoutproducinganARM.Theabstractiontakescareofa)keepingtheprocessalive,b)callingtheservice,c)takingcareofthewakelock,andd)transferringthebroadcastintenttotheservice.Intheendthisabstractionsimulates“callingamethodthatcanexecutewithouttimelimits”fromabroadcastevent.

AdditionalTopicsinBroadcastReceiversDuetospacelimitations,wearenotabletocoverallaspectsofbroadcastreceiversinthisbook.Onetopicwehaven’tcoveredatallisthesecurityopportunitiesavailabletorestrictbothsendingandreceivingbroadcasts.Youcanusetheexportattributeonareceivertoallowwhetheritcanbeinvokedfromexternalprocessesornot.Youcanalsoenableordisableareceivereitherthroughthemanifestfileorprogrammatically.WehavealsonotcoveredamethodcalledsendOrderBroadcastthatfacilitatescallingbroadcastreceiversinanorderincludingchainingthem.YoucanreadupontheseaspectsfromthemainAPIdocsfortheBroadcastReceiverclass.

Furthermore,inversion4oftheAndroidsupportlibrarySDKthereisaclasscalledLocalBroadcastManagerthatisusedtooptimizecallstobroadcastreceiversthatarestrictlylocal.Beinglocal,allsecuritylimitationsneednotbeconsidered.AspertheSDK,thereisalsosystem-leveloptimizationforwhenthisclassisused.

Alsoinversion4oftheAndroidsupportlibrarySDK,thereisaclasscalledWakefulBroadcastReceiverthatencapsulatessomeofthesameconceptsthatwehavecoveredforlong-runningserviceneeds.

ReferencesHerearehelpfulreferencestothetopicsthatarecoveredinthischapter:

http://developer.android.com/reference/android/content/BroadcastReceiver.htmlTheBroadcastReceiverAPI.YouwillfindatthislinkmoreaboutorderedbroadcastsandabouttheBroadcastReceiverlifecycle.Thisisanexcellentresource.

http://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.htmlAndroidAPIreference.

http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.htmlAndroidAPIreference.

http://developer.android.com/reference/android/app/Service.htmlTheServiceAPI.Thisreferenceisespeciallygoodtohavewhileworkingwithlong-runningservices.

http://developer.android.com/reference/android/app/NotificationManager.htmlTheNotificationManagerAPI.

http://developer.android.com/reference/android/app/Notification.htmlTheNotificationAPI.Youwillseeherethevariousoptionsavailableforworkingwithanotification,suchascontentviewsandsoundeffects.

http://developer.android.com/reference/android/widget/RemoteViews.htmlTheRemoteViewsAPI.RemoteViewsareusedtoconstructcustomdetailedviewsofnotifications.

http://www.androidbook.com/item/3514:Authors’researchonlong-runningservices.

http://www.androidbook.com/item/3482:Authors’researchonbroadcastreceivers.Thisnotealsoexplainshowtostartanactivityfromareceiver.

http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsfromthisbook.Forthischapter,lookforaZIPfilenamedProAndroid5_Ch16_TestReceivers.zip.ThisZIPfilehastwoprojects:TestBroadcastReceiverandStandaloneBroadcastReceiver.Thelatterisdependentontheformer,soinstalltheminthatorder.Thesourcecodesnippetsin

thischapterareannotatedwiththeirfilenamesandinwhatprojectstheyareavailable.

SummaryInthischapter,wehavecoveredbroadcastreceivers,notificationmanagers,andtheroleofserviceabstractioninputtingbroadcastreceiverstotheirbestuse.Wealsohavegivenapracticalabstractiontosimulatelongrunningbroadcastservicesoutofbroadcastreceivers.

Chapter17

ExploringtheAlarmManagerInAndroidanintentobjectisusedtostartaUIactivity,abackgroundservice,orabroadcastreceiver.Normallytheseintentsaretriggeredbyuseractions.InAndroidyoucanalsousealarmstotriggerbroadcastintents,mindyou,onlybroadcastintents.Theinvokedbroadcastreceiversthencanchoosetostartanactivityoraservice.

InthischapteryouwilllearnaboutthealarmmanagerAPI.AlarmmanagerAPIisusedtoscheduleabroadcastintenttogooffataparticulartime.Wewillrefertothisprocessofschedulingabroadcastintentataparticulartimeassettinganalarm.

Wewillalsoshowyouhowtoschedulealarmsthatrepeatatregularintervals.Wewillshowyouhowtocancelalarmsthatarealreadyset.

Whenanintentobjectisstoredtobeusedatalatertime,itiscalledapendingintent.Asalarmmanagersusependingintentsallthetimeyouwillgettoseetheusageandintricaciesofpendingintentsaswellinthischapter.

SettingUpaSimpleAlarmWewillstartthechapterwithsettinganalarmataparticulartimeandhavingitcallabroadcastreceiver.Oncethebroadcastreceiverisinvoked,youcanusetheinformationfromChapter16toperformbothsimpleandlong-runningoperationsinthatbroadcastreceiver.

GettingaccesstothealarmmanagerissimpleandisshowninListing17-1.

Listing17-1.GettingAccesstoanAlarmManager

//Infilename:SendAlarmOnceTester.javaAlarmManageram=(AlarmManager)

anyContextObject.getSystemService(Context.ALARM_SERVICE);

ThevariableanyContextObjectreferstoacontextobject.Forexample,ifyouareinvokingthiscodefromanactivitymenu,thecontextvariablewillbetheactivity.Tosetthealarmforaparticulardateandtime,youwillneedaninstanceintimeidentifiedbyaJavaCalendarobject.Listing17-2showsautilityfunctionthatgivesyouacalendarobjectforsomespecifiedtimeinstantafterthecurrenttime.

Listing17-2.AFewUsefulCalendarUtilities

//Infilename:Utils.javapublicclassUtils{publicstaticCalendargetTimeAfterInSecs(intsecs){

Calendarcal=Calendar.getInstance();cal.add(Calendar.SECOND,secs);returncal;}}

Inthedownloadableprojectforthischapter,youwillseelotmorecalendar-basedutilitiestoarriveatatimeinstanceinanumberofways.Now,weneedareceivertosetagainstthealarmthatweareplanningtoset.AsimplereceiverisshowninListing17-3.

Listing17-3.TestReceivertoTestAlarmBroadcasts

//Infilename:TestReceiver.javapublicclassTestReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}

Youwillneedtoregisterthisreceiverinthemanifestfileusingthe<receiver>tag,asshowninListing17-4.ReceiversarecoveredindetailinChapter16.

Listing17-4.RegisteringaBroadcastReceiver

<!--Infilename:AndroidManifest.xml--><receiverandroid:name=".TestReceiver"/>

InAndroidanalarmisreallyabroadcastintentthatisscheduledforalatertime.Whatreceivercomponentthisintentshouldinvokeisexplicitly(throughitsclassname)specifiedintheintent.Listing17-5showsanintentthatcanbeusedtoinvokethebroadcastreceiverthatwehadinListing17-3.

Listing17-5.CreatinganIntentPointingtoTestReceiver

//Infilename:SendAlarmOnceTester.javaIntentintent=newIntent(mContext,TestReceiver.class);intent.putExtra("message","SingleShotAlarm");

Wealsohaveanopportunitytoloadtheintentwith“extras”whilecreatingthisintent.Becauseanalarmmanagerstoresanintentforalateruse,weneedtocreateapendingintentoutofthisintentofListing17-5.Listing17-6showshowtocreateapendingintentfromastandardintent.

Listing17-6.CreatingaPendingIntent

//Infilename:SendAlarmOnceTester.javaPendingIntentpendingIntent=

PendingIntent.getBroadcast(mContext,//context,oractivity,orservice1,//requestid,usedfordisambiguatingthisintentintent,//intenttobedelivered0);//pendingintentflags

NoticethatwehaveaskedthePendingIntentclasstoconstructapendingintentthatissuitableforabroadcastexplicitly.TheothervariationsofcreatingapendingintentarelistedinListing17-7:

Listing17-7.MultipleAPIsforCreatingaPendingIntent

//usefultostartanactivityPendingIntentactivityPendingIntent=PendingIntent.getActivity(..args..);//usefultostartaservicePendingIntentservicePendingIntent=PendingIntent.getService(..args..);

InListing17-7,argumentstothemethodsgetActivity()andgetService()aresimilartotheargumentstothegetBroadcast()methodinListing17-6.Notethatalarmsrequireabroadcastpendingintentandnotanactivitypendingintentoraservicependingintent.

Wewilldiscusstherequestidargument,whichwesetto1inListing17-6,ingreaterdetaillaterinthechapter.Briefly,itisusedtoseparatetwointentobjectsthatareequalinallotherrespects.

Pendingintentflagshavelittleornoinfluenceonthealarmmanager.Recommendationistousenoflagsatallanduse0fortheirvalues.Theseintentflagsaretypicallyusefulincontrollingthelifetimeofthependingintent.However,inthiscase,thelifetimeismaintainedbythealarmmanager.Forexample,tocancelapendingintent,youaskthealarmmanagertocancelit.

OncewehavethetimeinstanceinmillisecondsasaCalendarobjectandthependingintentpointingtothereceiver,wecansetupanalarmbycallingtheset()methodofthealarmmanager.ThisisshowninListing17-8.

Listing17-8.UsingtheAlarmManagerset()Method

//Infilename:SendAlarmOnceTester.javaCalendarcal=Utils.getTimeAfterInSecs(30);//...othercodethatgetsthependingintentetcam.set(AlarmManager.RTC_WAKEUP,cal.getTimeInMillis(),pendingIntent);

Thefirstargumenttotheset()methodindicatesthewakeupnatureofthealarmandalsothereferenceclockthatwearegoingtobeusingforthealarm.Possiblevaluesforthis

argumentareAlarmManager.RTC_WAKEUP,AlarmManager.RTC,AlarmManager.ELAPSED_REALTIME,AlarmManager.ELAPSED_REALTIME_WAKEUP.

Theelapsedwordintheseconstantsreferstothetimeinmillisecondssincethedeviceisrecentlybooted.So,itreferstothedeviceclock.TheRTCtimereferstothehumanclock/timethatyouseeonthedevicewhenyoucheckyourclockonthedevice.TheWAKEUPwordintheseconstantsreferstothenatureofthealarm,suchaswhetherthealarmshouldwakeupthedeviceorjustdeliveritatthefirstopportunitywhenthedeviceeventuallywakesup.Takentogether,theRTC_WAKEUPindicatestheuseofreal-timeclockandthedeviceshouldwakeup.TheconstantELAPSED_REALTIMEmeansusethedeviceclockanddon’twakeupthedevice;instead,deliverthealarmatthefirstopportunity.

WhenthismethodofListing17-8iscalled,thealarmmanagerwillinvoketheTestReceiverinListing17-3,30secondsafterthecalendartimewhenthemethodwascalledandalsowakesupthedeviceifitisasleep.

SettingOffanAlarmRepeatedlyLet’snowconsiderhowwecansetanalarmthatgoesofrepeatedly;seeListing17-9.

Listing17-9.SettingaRepeatingAlarm

publicvoidsendRepeatingAlarm(){Calendarcal=Utils.getTimeAfterInSecs(30);

//GetanintenttoinvokethereceiverIntentintent=newIntent(this.mContext,TestReceiver.class);intent.putExtra("message","RepeatingAlarm");

intrequestid=2;PendingIntentpi=this.getDistinctPendingIntent(intent,requestid);//Schedulethealarm!AlarmManageram=(AlarmManager)this.mContext.getSystemService(Context.ALARM_SERVICE);

am.setRepeating(AlarmManager.RTC_WAKEUP,

cal.getTimeInMillis(),5*1000,//5secsrepeatpi);}

protectedPendingIntentgetDistinctPendingIntent(Intentintent,int

requestId){

PendingIntentpi=PendingIntent.getBroadcast(mContext,//context,oractivityrequestId,//requestidintent,//intenttobedelivered0);returnpi;}

KeyelementsofthecodeinListing17-9arehighlighted.ArepeatingalarmissetbyinvokingthesetRepeating()methodonthealarmmanagerobject.Theprimaryinputtothismethodisapendingintentpointingtoareceiver.WehaveusedthesameintentthatwascreatedinListing17-5,theonepointingtotheTestReceiver.However,whenwemakeapendingintentoutoftheintentinListing17-5,wealtertheuniquerequestcodetoavalueof2.Ifwedon’tdothis,wewillseeabitofoddbehaviorwhichweshallexplainnow.Sayweintendtoinvokethesamereceiverthroughtwodifferentalarms:onealarmthatgoesoffonlyonceandanotheralarmthatgoesoffrepeatedly.Becausebothalarmstargetthesamereceivertheyneedtobeusinganintentthatpointstothesamereceiver.Twointentsthatpointtothesamereceiver,withoutanyotherdifferencebetweenthem,isconsideredthesameintent.So,whenwetellthealarmmanagertosetthealarmonintent1asaone-timealarmandthensetthealarmonintent2asarepeatedalarm,wemightbeundertheimpressionthattheyaretwodifferentalarms.Internally,however,bothalarmspointtothesameintentvalue,asintent1andintent2arethesameintheirvalues.Thisiswhyanalarmispracticallythesameasitsintentonwhichitisset(especiallybyvalue).Asaresult,thelateralarmoverridesthefirstalarmiftheintentsareequivalent.

Again,twointentsareconsideredthesameiftheyhavethesameaction,type,data,categories,orclass.Theextrasarenotincludedinfiguringouttheuniquenessofintents.Further,twopendingintentsareconsideredthesameiftheirunderlyingintentsarethesameandtherequestIDsmatch.BecausewecanusetherequestIDtodistinguishtwopendingintents,thecodeinListing17-8overcomesthesimilarityofsourceintentsbyusingtherequestidargument.ThisrequestidargumenttothePendingIntentAPIwillseparateonependingintentfromtheotherpendingintentwhenallelsematches.

Thisallshouldmakesenseifyouweretoseethependingintent(byvaluenotbyitsJavaobjectreference)itselfasthealarmonwhichyouaresettingdifferenttimes.

CancellinganAlarmCodeinListing17-10isusedtocancelanalarm.

Listing17-10.CancellingaRepeatingAlarm

publicvoidcancelRepeatingAlarm(){//Getanintentthatwasoriginally//usedtoinvokeTestReceiverclassIntentintent=newIntent(this.mContext,

TestReceiver.class);

//Tocancel,extraisnotnecessarytobefilledin//intent.putExtra("message","RepeatingAlarm");

PendingIntentpi=this.getDistinctPendingIntent(intent,2);

//Cancelthealarm!AlarmManageram=(AlarmManager)

this.mContext.getSystemService(Context.ALARM_SERVICE);am.cancel(pi);}

Tocancelanalarm,wehavetoconstructapendingintentfirstandthenpassittothealarmmanagerasanargumenttothecancel()method.However,youmustpayattentiontomakesurethatthePendingIntentisconstructedtheexactsamewaywhensettingthealarm,includingtherequestcodeandtargetedreceiver.

Inconstructingthecancelintent,youcanignoretheintentextrasfromtheoriginalintent(Listing17-10),becauseintentextrasdon’tplayaroleintheuniquenessofanintent,andhencecancellingthatintent.

UnderstandingExactnessofAlarmsPriortoAPI19,Androidfiredthealarmsascloseaspossibletothespecifiedtime.SinceAPI19,alarmsthatareclosetoeachotherarebundledforbatterylife.Ifyouneedtheolderbehavior,thereisaversionofset()methodcalledsetExact().ThereisalsoamethodcalledsetWindow()thatallowsroomforefficienciesandalsoallowsaguaranteedwindow.Similarly,themethodsetRepeating()isnowinexact.UnlikethesetExact()method,thereisnoexactversionforsetRepeating().Ifyouhavesuchaneed,youhavetousethesetExact()andrepeatityourselfmultipletimes.

UnderstandingPersistenceofAlarmsAnothernoteonalarmsisthattheyarenotsavedacrossdevicereboots.Thismeansyouwillneedtosavethealarmsettingsandpendingintentsinapersistentstoreandreregisterthembasedondevicerebootbroadcastactions,andpossiblytime-changebroadcastactions(e.g.,intent.ACTION_BOOT_COMPLETED,intent.ACTION_TIME_CHANGED,intent.ACTION_TIMEZONE_CHANGED).

References

Thefollowingreferenceswillhelpyoulearnmoreaboutthetopicsdiscussedinthischapter:

http://developer.android.com/reference/android/app/AlarmManager.htmlThealarmmanagerAPI.Youwillseeheresignaturesformethodslikeset,setRepeating,andcancel.

http://developer.android.com/reference/android/app/PendingIntent.htmlHowtoconstructapendingintent.Don’tpaytoomuchattentiontothependingintentflags;theyarenotthatcriticaltothealarmmanager.

http://androidbook.com/item/1040:Quickexamplesandreferencesforworkingwithdateandtimeclasses.

http://androidbook.com/item/3503:Ourresearchonalarmmanagers.

http://androidbook.com/proandroid5/projects:Alistofdownloadableprojectsfromthisbook.Forthischapter,lookforaZIPfilenamedProAndroid5_Ch17_TestAlarmManager.zip.

SummaryThischapterexploredtheAlarmManagerAPI,whichyouusetosetupandcancelalarms.Thischaptershowedyouhowtoconnectanalarmtoabroadcastservice.Thischapteralsoshowedyouhowalarmsarecloselyrelatedtointents.

Chapter18

Exploring2DAnimationAnimationallowsanobjectonascreentochangeitscolor,position,size,ororientationovertime.AnimationcapabilitiesinAndroidarepractical,fun,andsimple.Theyareusedfrequentlyinapplications.

Android2.3andpriorreleasessupportthreetypesofanimation:frame-by-frameanimation,whichoccurswhenaseriesofframesisdrawnoneaftertheotheratregularintervals;layoutanimation,whereyouanimatethelayoutoftheviewsinsideacontainersuchaslistsandtables;andviewanimation,inwhichanyviewcanbeanimated.Inlayoutanimationthefocusisnotanygivenviewbutthewayviewscometogethertoformthecompositelayout.Android3.0enhancedanimationbyextendingittoanyJavapropertyincludingthepropertiesofUIelements.Wewillcoverthepre-2.3featuresfirstandthencoverthe3.0featuresrightafter.Bothfeaturesareapplicablebasedonyourusecase.

ExploringFrame-by-FrameAnimationFrame-by-frameanimationiswhereaseriesofimagesareshowninsuccessionatquickintervalssothatthefinaleffectisthatofanobjectmovingorchanging.Figure18-1showsasetofcircleseachwithaballatadifferentposition.Withafewoftheseimages(whicharetheframes)youcanuseanimationtohavetheballgoingaroundthecircle.

Figure18-1.Exampleimageframesforanimation

EachcircleinFigure18-1isaseparateimage.Givetheimageabasenameofcolored_ballandstoreeightoftheseimagesinthe/res/drawablesubdirectorysothatyoucanaccessthemusingtheirresourceIDs.Thenameofeachimagewillhavethepatterncolored-ballN,whereNisthedigitrepresentingtheimagenumber.TheanimationactivityweareplanningwilllooklikeFigure18-2.

Figure18-2.Aframe-by-frameanimationtestharness

PrimarycontrolinFigure18-2istheanimationviewshowingtheballplacedonanoval/circle.Buttonatthetopisusedtostartandstoptheanimation.Thereisadebugscratchpadatthetoptologevents.Listing18-1showsthelayoutusedtocreatetheactivityinFigure18-2.

Listing18-1.XMLLayoutFilefortheFrameAnimationExample

<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/frame_animations_layout.xmlDownload:ProAndroid5_ch18_TestFrameAnimation.zip--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:id="@+id/textViewId1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="DebugScratchPad"/><Buttonandroid:id="@+id/startFAButtonId"android:layout_width="fill_parent"

android:layout_height="wrap_content"android:text="StartAnimation"/><ImageViewandroid:id="@+id/animationImage"android:layout_width="fill_parent"android:layout_height="wrap_content"/></LinearLayout>

Thefirstcontrolisthedebug-scratchtextcontrol,whichisasimpleTextView.Youthenaddabuttontostartandstoptheanimation.ThelastviewistheImageView,whichisusedtoplaytheanimation.

InAndroid,frame-by-frameanimationisimplementedthroughtheclassAnimationDrawable.ThisclassisaDrawable.Theseobjectsarecommonlyusedasbackgroundsforviews.AnimationDrawable,inadditiontobeingaDrawable,cantakealistofotherDrawableresources(likeimages)andrenderthematspecifiedintervals.TousethisAnimationDrawableclass,startwithasetofDrawableresources(forexample,asetofimages)placedinthe/res/drawablesubdirectory.YouwillthenconstructanXMLfilethatdefinestheAnimationDrawableusingalistoftheseimages(seeListing18-2).ThisXMLfileneedstobeplacedinthe/res/drawablesubdirectoryaswell.

Listing18-2.XMLFileDefiningtheListofFramestoBeAnimated

<!--filename:/res/drawable/frame_animation.xmlDownload:ProAndroid5_ch18_TestFrameAnimation.zip--><animation-listxmlns:android="http://schemas.android.com/apk/res/android"

android:oneshot="false"><itemandroid:drawable="@drawable/colored_ball1"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball2"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball3"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball4"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball5"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball6"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball7"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball8"android:duration="50"/></animation-list>

Eachframepointstooneofthecolored-ballimagesyouhaveassembledthroughtheirresourceIDs.Theanimation-listtaggetsconvertedintoanAnimationDrawableobjectrepresentingthecollectionofimages.YouthenneedtosetthisAnimationDrawableasabackgroundresourceforourImageViewcontrolintheactivitylayout.AssumingthatthefilenameforthisXMLfileisframe_animation.xmlandthatitresidesinthe/res/drawablesubdirectory,youcanusethefollowingcodetosettheAnimationDrawableasthebackgroundoftheImageView:

view.setBackgroundResource(R.drawable.frame_animation);//SeeListing18-3

Withthiscode,AndroidrealizesthattheresourceIDR.drawable.frame_animationisanXMLresourceandaccordinglyconstructsasuitableAnimationDrawableJavaobjectforitbeforesettingitasthebackground.Oncethisisset,youcanaccessthisAnimationDrawableobjectbydoingagetontheviewobjectlikethis:

ObjectbackgroundObject=view.getBackground();AnimationDrawablead=(AnimationDrawable)backgroundObject;

OnceyouhavetheAnimationDrawableobject,youcanuseitsstart()andstop()methodstostartandstoptheanimation.Herearetwootherimportantmethodsonthisobject:

setOneShot(boolean);addFrame(drawable,duration);

ThesetOneShot(true)methodrunstheanimationonceandthenstops.TheaddFrame()methodaddsanewframeusingaDrawableobjectandsetsitsdisplayduration.ThefunctionalityoftheaddFrame()methodresemblesthatoftheXMLtagandroid:drawableinListing18-2.Putthisalltogethertogetthecompletecodeforourframe-by-frameanimationactivityofFigure18-1.

Listing18-3.CompleteCodefortheFrame-by-FrameAnimationTestHarness

//filename:FrameAnimationActivity.java//Download:ProAndroid5_ch18_TestFrameAnimation.zippublicclassFrameAnimationActivityextendsActivity{

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.frame_animations_layout);this.setupButton();}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.startFAButtonId);

b.setOnClickListener(newButton.OnClickListener(){publicvoidonClick(Viewv){animate();}});}privatevoidanimate(){ImageViewimgView=(ImageView)findViewById(R.id.animationImage);imgView.setVisibility(ImageView.VISIBLE);imgView.setBackgroundResource(R.drawable.frame_animation);

AnimationDrawableframeAnimation=(AnimationDrawable)imgView.getBackground();if(frameAnimation.isRunning()){frameAnimation.stop();}else{frameAnimation.stop();frameAnimation.start();}}}//eof-class

animate()methodinListing18-3locatestheImageViewintheactivityandsetsitsbackgroundtotheAnimationDrawableidentifiedbytheresourceR.drawable.frame_animation.ThisanimationresourceIDpointstotheearlieranimationdefinitioninListing18-3.TherestofthecodeinthemethodretrievesthisAnimationDrawableobjectandcallsanimationmethodsonthatobject.InthesameListing18-3,Start/Stopbuttonissetupsothatifanimationisrunning,thebuttoncouldstopit;ifanimationisnotrunning,thebuttoncouldstartit.IfyousettheoneshotattributeintheanimationdefinitioninListing18-2totrue,theanimationstopsafteronce.

ExploringLayoutAnimationLayoutAnimationisusedtoanimatetheviewsinanAndroidlayout.YoucanusethistypeofanimationforexamplewithcommonlayoutcontrolslikeListViewandGridView.Unlikeframe-by-frameanimation,layoutanimationisnotachievedthroughrepeatingframesbutbychangingthetransformationmatrixofaview.EveryviewinAndroidhasatransformationmatrixthatmapstheviewtothescreen.Bychangingthismatrixyoucanaccomplishscaling,rotation,andmovement(translation)oftheview.Thistypeofanimationthatreliesonchangingpropertiesandredrawinganimageisreferredtoastweeninganimation.EssentiallyLayoutAnimationistweeninganimationofthetransformationmatrixoftheviewsinalayout.ALayoutAnimationthatisspecifiedonalayoutisappliedtoalltheviewsinthatlayout.

Thesearethetweeninganimationtypesthatcanbeappliedtoalayout:

Scaleanimation:Usedtomakeaviewsmallerorlargereitheralongthexaxis,ontheyaxis,oronboth.Youcanalsospecifythepivotpointaroundwhichyouwanttheanimationtotakeplace.

Rotateanimation:Usedtorotateaviewaroundapivotpointbyacertainnumberofdegrees.

Translateanimation:Usedtomoveaviewalongthexaxisortheyaxis.

Alphaanimation:Usedtochangethetransparencyofaview.

TheseanimationsaredefinedasXMLfilesinthe/res/animsubdirectory.Listing18-4showsascaleanimationdeclaredinanXMLfile.

Listing18-4.AScaleAnimationDefinedinanXMLFileat/res/anim/scale.xml

<setxmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_interpolator"><scaleandroid:fromXScale="1"android:toXScale="1"android:fromYScale="0.1"android:toYScale="1.0"android:duration="500"android:pivotX="50%"android:pivotY="50%"android:startOffset="100"/></set>

ParametersintheanimationXMLshavea“from”anda“to”flavortoindicatestartandendvaluesofthatproperty.Otherpropertiesofananimationalsoincludeanimationdurationandatimeinterpolator.InterpolatorsdeterminetherateofchangeoftheanimatedargumentsuchasscaleinListing18-4duringanimation.Wewillcoverinterpolatorsshortly.XMLfileinListing18-4canbeassociatedwithalayouttoanimatethatlayout’sconstituentviews.

NoteAnimationsliketheScaleanimationinListing18-4arerepresentedasJavaclassesintheandroid.view.animationpackage.JavadocumentationfortheseclassesdescribesnotonlyJavamethodsbutalsotheallowedXMLargumentsforeachtypeofanimation.

WecanusetheListViewinFigure18-3totestanumberoflayoutanimations.Thisactivityiswhatyouseewhenyourunthesampleprojectforthischapter.ProAndroid5_ch18_TestLayoutAnimation.zip

Figure18-3.TheListViewtobeAnimated

ThelayoutforthisactivityisinListing18-5.

Listing18-5.ListViewXMLLayoutFile

<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/list_layout.xmlproject:ProAndroid5_ch18_TestLayoutAnimation.zip--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><ListViewandroid:id="@+id/list_view_id"android:layout_width="fill_parent"android:layout_height="fill_parent"/></LinearLayout>

Listing18-5showsasimpleLinearLayoutwithasingleListViewinit.Theactivitycodetoshowthelayoutfrom18-5asFigure18-3isinListing18-6.

Listing18-6.Layout-AnimationActivityCode

//filename:LayoutAnimationActivity.java//project:ProAndroid5_ch18_TestLayoutAnimation.zippublicclassLayoutAnimationActivityextendsActivity{

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.list_layout);setupListView();}privatevoidsetupListView(){String[]listItems=newString[]{"Item1","Item2","Item3","Item4","Item5","Item6",};ArrayAdapter<String>listItemAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems);ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.setAdapter(listItemAdapter);}}

Let’sseenowhowtoapplyscaleanimationfromListing18-4tothisListView.TheListViewrequiresanotherXMLfilethatactsasamediatorbetweenitselfandthescaleanimationinListing18-4.ThisisbecausetheanimationsdefinedinListing18-4aregenericandapplytoanyview.Ontheotherhandalayoutisacollectionofviews.SothemediatorlayoutanimationXMLfileinListing18-7reusesthegenericanimationXMLfileandspecifiestheadditionalattributesthatareapplicabletoacollectionofviews.ThismediatorlayoutanimationXMLfileisshowninListing18-9.

Listing18-7.Layout-ControllerXMLFile

<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/anim/list_layout_controller.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><layoutAnimationxmlns:android="http://schemas.android.com/apk/res/android"

android:delay="100%"android:animationOrder="reverse"android:animation="@anim/scale"/>

ThisXMLfileneedstobein/res/animsubdirectory.ThisXMLfilespecifiesthattheanimationinthelistshouldproceedinreverse,andtheanimationforeachitemshouldstartwitha100%delaywithrespecttothetotalanimationduration.A100%durationensuresthattheanimationofoneitemiscompletebeforetheanimationofthenextitemstarts.Youcanchangethispercentagetosuittheneedsofyouranimation.Anythingless

than100%willresultinanoverlappinganimationofitems.ThismediatorXMLfilealsoreferstotheindividualanimationfile,scale.xml(Listing18-4)throughtheresourcereference@anim/scale.Listing18-8showshowtoattachtheanimationofListing18-4totheactivitylayoutofListing18-5viathemediatorofListing18-7.

Listing18-8.TheUpdatedCodeforthelist_layout.xmlFile

<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/list_layout.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><ListViewandroid:id="@+id/list_view_id"android:persistentDrawingCache="animation|scrolling"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layoutAnimation="@anim/list_layout_controller"/></LinearLayout>

InListing18-8android:layoutAnimationisthetagthatpointstothemediatingXMLfileofListing18-7,whichinturnpointstothescale.xmlofListing18-5.InListing18-8AndroidSDKdocsrecommendsettingthepersistentDrawingCachetagonthelistviewtooptimizeforanimationandscrolling.IfyouweretoruntheapplicationProAndroid5_ch18_TestLayoutAnimation.zip,youwouldseethescaleanimationtakeeffectontheindividuallistitemsastheactivitygetsloaded.Wehavesettheanimationdurationto500mssothatscalechangecanbeobservedaseachlistitemisdrawn.

Withthissampleprogramyoucanexperimentwithdifferentanimationtypes.YoucantryalphaanimationwiththecodeinListing18-9.

Listing18-9.Thealpha.xmlFiletoTestAlphaAnimation

<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/anim/alpha.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><alphaxmlns:android="http://schemas.android.com/apk/res/android"

android:interpolator="@android:anim/accelerate_interpolator"android:fromAlpha="0.0"android:toAlpha="1.0"android:duration="1000"/>

Alphaanimationcontrolsfading(ofcolor).InListing18-9alphaanimationcolorgoes

frominvisibletofullintensityin1second.Don’tforgettochangethemediatorXMLfile(seeListing18-7)topointtothenewanimationfileifyouintendtousethesamemediatorfile.

Listing18-10showsananimationthatcombinesachangeinpositionwithachangeincolorgradient.

Listing18-10.CombiningTranslateandAlphaAnimationsThroughanAnimationSet

<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/anim/alpha_translate.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><setxmlns:android="http://schemas.android.com/apk/res/android"

android:interpolator="@android:anim/accelerate_interpolator"><translateandroid:fromYDelta="-100%"android:toYDelta="0"android:duration="500"/><alphaandroid:fromAlpha="0.0"android:toAlpha="1.0"android:duration="500"/></set>

NoticetwoanimationsareintheanimationsetofListing18-10.Translateanimationwillmovethetextfromtoptobottominitscurrentlyallocateddisplayspace.Alphaanimationwillchangethecolorgradientfrominvisibletovisibleasthetextitemdescendsintoitsslot.Toseethisanimationinaction,changethelayoutAnimationmediatorXMLfilewithreferencetofilename@anim/alpha_translate.xml.Listing18-11showsthedefinitionforarotationanimation.

Listing18-11.RotateAnimationXMLFile

<!--file:/res/anim/rotate.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><rotatexmlns:android="http://schemas.android.com/apk/res/android"

android:interpolator="@android:anim/accelerate_interpolator"android:fromDegrees="0.0"android:toDegrees="360"android:pivotX="50%"android:pivotY="50%"android:duration="500"/>

Listing18-11spinseachtextiteminthelistonefullcirclearoundthemidpointofthetextitem.Let’stalkabouttheinterpolatorsthatyouhaveseenusedintheanimationXMLfiles.

UnderstandingInterpolatorsInterpolatorstellhowapropertychangesovertimefromitsstartvaluetotheendvalue.Willthechangeoccurlinearlyorexponentially?Willthechangestartquicklyandslowdowntowardtheend?

AlphaanimationinListing18-9identifiestheinterpolatoras

accelerate_interpolator.ThereisacorrespondingJavaobjectthatdefinesthebehaviorofthisinterpolator.Aswe’vespecifiedthisinterpolatorasaresourcereferenceinListing18-9,theremustbeafilecorrespondingtothe@anim/accelerate_interpolatorthatdescribeswhatthisJavaobjectisandwhatadditionalparametersitmighttake.Listing8-12showstheresourceXMLfiledefinitionpointedtobytheresourcereference@android:anim/accelerate_interpolator:

Listing18-12.AnInterpolatorDefinitionasanXMLResource

<accelerateInterpolatorxmlns:android="http://schemas.android.com/apk/res/android"factor="1"/>

YoucanseethisXMLfileinthesubdirectory/res/anim/accelerate_interpolator.xmlintherootAndroidSDKpackage.(Caution:Thisfilecouldlookdifferentlydependingontherelease.)TheaccelerateInterpolatorXMLtagcorrespondstotheJavaclassandroid.view.animation.AccelerateInterpolator.YoucanlookupthecorrespondingJavadocumentationtoseewhatXMLtagsareavailable.Thisinterpolator’sgoalistoprovideamultiplicationfactorgivenatimeintervalbasedonahyperboliccurve.ThesourcecodesnippetinListing18-13forthisinterpolatorillustratesthis.(Caution:ThiscodecouldlookdifferentdependingontheAndroidrelease.)

Listing18-13.SampleCodefromAccelerateInterpolatorintheCoreAndroidSDK

publicfloatgetInterpolation(floatinput){if(mFactor==1.0f){return(float)(input*input);}else{return(float)Math.pow(input,2*mFactor);}}

EveryinterpolatorimplementsthegetInterpolationmethoddifferently.IncaseoftheAccelerateInterpolator,iftheinterpolatorissetupintheresourcefilewithafactorof1.0,itwillreturnthesquareoftheinputateachinterval.Otherwise,itwillreturnapoweroftheinputthatisfurtherscaledbythefactoramount.Ifthefactoris1.5,youwillseeacubicfunctioninsteadofasquarefunction.

ThesupportedinterpolatorsincludeAccelerateDecelerateInterpolator,AccelerateInterpolator,CycleInterpolator,DecelerateInterpolator,LinearInterpolator,AnticipateInterpolator,AnticipateOvershootInterpolator,BounceInterpolator,andOvershootInterpolator.

Toseehowflexibletheseinterpolatorscanbe,takeaquicklookinListing18-14atthe

BounceInterpolatorwhichbouncestheobject(thatis,movesitbackandforth)towardtheendoftheanimationcycle:

Listing18-14.BounceInterpolatorImplementationintheCoreAndroidSDK

publicclassBounceInterpolatorimplementsInterpolator{privatestaticfloatbounce(floatt){returnt*t*8.0f;}publicfloatgetInterpolation(floatt){t*=1.1226f;if(t<0.3535f)returnbounce(t);elseif(t<0.7408f)returnbounce(t-0.54719f)+0.7f;elseif(t<0.9644f)returnbounce(t-0.8526f)+0.9f;elsereturnbounce(t-1.0435f)+0.95f;}}

YoucanfindthebehaviorofthesevariousinterpolatorsdescribedatthefollowingURL:

http://developer.android.com/reference/android/view/animation/package-summary.html

JavadocumentationforeachoftheseclassesalsopointsouttheXMLtagsavailabletocontrolthem.

ExploringViewAnimationThroughviewanimationyoucananimateaviewbymanipulatingitstransformationmatrix.Atransformationmatrixislikealensthatprojectsaviewontothedisplay.Atransformationmatrixcanaffecttheprojectedviewsscale,size,position,andcolor.

Identitytransformationmatrixpreservestheoriginalview.Youstartwithanidentitymatrixandapplyaseriesofmathematicaltransformationsinvolvingsize,position,andorientation.Youthensetthefinalmatrixasthetransformationmatrixfortheviewyouwanttotransform.

Androidexposesthetransformationmatrixforaviewbyallowingregistrationofananimationobjectwiththatview.Theanimationobjectwillbepassedtothetransformationmatrix.

ConsiderFigure18-4asademonstrationofviewanimation.TheStartAnimationbuttonanimatesthelistviewtostartsmallinthemiddleofthescreenandgraduallyfillthefullspace.Listing18-15showstheXMLlayoutfileusedforthisactivity.

Figure18-4.AViewAnimationActivity

Listing18-15.XMLLayoutFilefortheView-AnimationActivity

<?xmlversion="1.0"encoding="utf-8"?><!--filen:at/res/layout/list_layout.xml(ProAndroid5_ch18_TestViewAnimation.zip)--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><Buttonandroid:id="@+id/btn_animate"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="StartAnimation"/><ListViewandroid:id="@+id/list_view_id"android:persistentDrawingCache="animation|scrolling"android:layout_width="fill_parent"android:layout_height="fill_parent"/></LinearLayout>

Listing18-16showstheactivitycodethatloadsthislayout.

Listing18-16.CodefortheView-AnimationActivity,BeforeAnimation

//filename:ViewAnimationActivity.java(ProAndroid5_ch18_TestViewAnimation.zip)publicclassViewAnimationActivityextendsActivity{

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.list_layout);setupListView();this.setupButton();}privatevoidsetupListView(){String[]listItems=newString[]{"Item1","Item2","Item3","Item4","Item5","Item6",};ArrayAdapter<String>listItemAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems);ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.setAdapter(listItemAdapter);}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.btn_animate);b.setOnClickListener(newButton.OnClickListener(){publicvoidonClick(Viewv){//animateListView();}});}}

WiththiscodeyouwillseetheUIaslaidoutinFigure18-4.ToaddanimationtotheListViewshowninFigure18-4weneedaclassthatderivesfromandroid.view.animation.Animation.Listing18-17showsthisclass.

Listing18-17.CodefortheViewAnimationClass

//filename:ViewAnimation.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{

@Override

publicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();matrix.setScale(interpolatedTime,interpolatedTime);}}

InListing18-7initializemethodisacallbackmethodwiththedimensionsoftheview.Animationparameterscanbeinitializedhere.Heretheanimationdurationissetto2.5seconds.WehavesettheanimationeffecttoremainintactaftertheanimationcompletesbysettingFillAftertotrue.We’vesetalinearinterpolator.Allofthesepropertiescomefromthebaseandroid.view.animation.Animationclass.

ThemainpartoftheanimationoccursintheapplyTransformationmethod.AndroidSDKcallsthismethodagainandagaintosimulateanimation.EverytimeAndroidcallsthemethod,interpolatedTimehasadifferentvalue.Thisvaluechangesfrom0to1dependingonwheretheanimationisinthe2.5-seconddurationthatyousetduringinitialization.WheninterpolatedTimeis1,theanimationisattheend.Ourgoalinthismethodistochangethetransformationmatrixthatisavailablethroughthetransformationobjectcalledt.Firstgetthematrixandchangesomethingaboutit.Whentheviewgetspainted,thenewmatrixwilltakeeffect.MethodsavailableontheMatrixobjectaredocumentedintheSDKat

http://developer.android.com/reference/android/graphics/Matrix.html

InListing18-17,thecodethatchangesthematrixis

matrix.setScale(interpolatedTime,interpolatedTime);

setScalemethodtakestwoparameters:thescalingfactorinthexdirectionandthescalingfactorintheydirection.BecauseinterpolatedTimegoesbetween0and1,youcanusethatvaluedirectlyasthescalingfactor.Atthestartoftheanimation,thescalingfactoris0inboththexandydirections.Halfwaythroughtheanimation,thisvaluewillbe0.5inbothxandydirections.Attheendofanimation,theviewwillbeatitsfullsizebecausethescalingfactorwillbe1inboththexandydirections.TheendresultofthisanimationisthattheListViewstartsouttinyandgrowstofullsize.Listing18-18showsthefunctionyouneedtoaddtheactivityclassinListing18-15andcallitfromthebuttonclick.

Listing18-18.CodefortheView-AnimationActivity,IncludingAnimation

privatevoidanimateListView(){ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.startAnimation(newViewAnimation());}

NoteInthissectiononViewAnimationwearegoingtosuggestalternateimplementationsfortheViewAnimationclassofListing18-18.InthesuppliedprojecttherearevariationsofthisclassavailableasViewAnimation,ViewAnimation1,ViewAnimation2,andViewAnimation3.Codesnippetsinthesubsequentdiscussionswillindicateincommentswhichoftheseclassesholdthatcode.Thereisonlyonemenuiteminthesampleprojectforanimation.TotesteachvariationyouhavetoreplacetheViewAnimation()classinListing18-18withtherespectiveversionofitandreruntheprogramtoseethealteredanimation.

WhenyourunthecodewiththeViewAnimationclassasinListing18-17,youwillnoticesomethingodd.Insteadofuniformlygrowinglargerfromthemiddleofthescreen,theListViewgrowslargerfromthetop-leftcorner.Thisisbecausetheoriginformatrixoperationsisatthetop-leftcorner.Togetthedesiredeffect,youfirsthavetomovethewholeviewsothattheview’scentermatchestheanimationcenter(topleft).Then,youapplythematrixandmovetheviewbacktothepreviouscenter.TherewrittencodefromListing18-16fordoingthisisshowninListing18-19.

Listing18-19.ViewAnimationusingpreTranslateandpostTranslate

//filename:ViewAnimation1.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{

floatcenterX,centerY;publicViewAnimation(){}

@Overridepublicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);centerX=width/2.0f;centerY=height/2.0f;setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();

matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);}}

ThepreTranslateandpostTranslatemethodssetupamatrixbeforethescaleoperationandafterthescaleoperation.Thisisequivalenttomakingthreematrixtransformationsintandem.ConsiderthefollowingcodeinListing18-20

Listing18-20.StandardpatternforPreandPostTranslateofTransformationMatrices

matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);

CodeinListing18-20isequivalentto

movetoadifferentcenterscaleitmovetotheoriginalcenter

Youwillseethispatternofpreandpostappliedoften.YoucanalsoaccomplishthisresultusingothermethodsontheMatrixclass,butthistechniqueiscommonasitissuccinct.

TheMatrixclassallowsyounotonlytoscaleaviewbutalsotomoveitaroundthroughtranslatemethodsorchangeitsorientationthroughrotatemethods.Youcanexperimentwiththesemethodsandseetheresultinganimations.Theanimationspresentedinthepreceding“LayoutAnimation”sectionareallimplementedinternallyusingthemethodsonthisMatrixclass.

UsingCameratoProvideDepthPerceptionin2DThegraphicspackageinAndroidprovidesanothertransformationmatrix–relatedfeaturethroughtheCameraclass.Thisclassprovidesdepthperceptiontoa2Dview.YoucantakeourListViewexampleandmoveitbackfromthescreenby10pixelsalongthezaxisandrotateitby30degreesaroundtheyaxis.Listing18-21isanexampleofmanipulatingthetransformationmatrixusingaCamera.

Listing18-21.UsingCameraObject

//filename:ViewAnimation2.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{

floatcenterX,centerY;Cameracamera=newCamera();publicViewAnimation(floatcx,floatcy){centerX=cx;centerY=cy;

}@Overridepublicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();camera.save();camera.translate(0.0f,0.0f,(1300-1300.0f*interpolatedTime));camera.rotateY(360*interpolatedTime);camera.getMatrix(matrix);

matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);camera.restore();}}

ThiscodeanimatestheListViewbyfirstplacingtheview1,300pixelsbackonthezaxisandthenbringingitbacktotheplanewherethezcoordinateis0.Whiledoingthis,thecodealsorotatestheviewfrom0to360degreesaroundtheyaxis.Thecamera.translate(x,y,z)methodinListing18-21tellsthecameraobjecttotranslatetheviewsuchthatwheninterpolatedTimeis0(atthebeginningoftheanimation),thezvaluewillbe1300.Astheanimationprogresses,thezvaluewillgetsmallerandsmalleruntiltheend,whentheinterpolatedTimebecomes1andthezvaluebecomes0.

Themethodcamera.rotateY(360*interpolatedTime)takesadvantageof3Drotationaroundanaxisbythecamera.Atthebeginningoftheanimation,thisvaluewillbe0.Attheendoftheanimation,itwillbe360.

Themethodcamera.getMatrix(matrix)takestheoperationsperformedontheCamerasofarandimposesthoseoperationsonthematrixthatispassedin.Oncethecodedoesthat,thematrixhasthetranslationsitneedstogettheendeffectofhavingaCamera.NowtheCameraobjectisnolongernecessarybecausethematrixhasalltheoperationsembeddedinit.Then,youdothepreandpostonthematrixtoshiftthecenterandbringitback.Attheend,yousettheCameratoitsoriginalstatethatwassavedearlier.WiththiscodeinListing18-21youwillseetheListViewarrivingfromthecenteroftheviewinaspinningmannertowardthefrontofthescreen.Asthisversion

ofViewAnimationtakesadditionalconstructionarguments,Listing18-22showshowtoinvokethisversionoftheAnimationView:

Listing18-22.ViewAnimationusingpreTranslateandpostTranslate

//filename:ViewAnimationActivity.java//project:ProAndroid5_ch18_TestViewAnimation.zipListViewlv=(ListView)this.findViewById(R.id.list_view_id);

floatcx=(float)(lv.getWidth()/2.0);floatcy=(float)(lv.getHeight()/2.0);lv.startAnimation(newViewAnimation(cx,cy));

Aspartofourdiscussionaboutviewanimation,weshowedyouhowtoanimateanyviewbyextendinganAnimationclassandthenapplyingittoaview.Inadditiontolettingyoumanipulatematrices(bothdirectlyandindirectlythroughaCameraclass),theAnimationclassalsoletsyoudetectvariousstagesinananimation.Wewillcoverthisnext.

ExploringtheAnimationListenerClassAndroidSDKhasalistenerinterface,AnimationListener,tomonitoranimationevents.Listing18-23demonstratestheseanimationeventsbyimplementingtheAnimationListenerinterface.

Listing18-23.AnImplementationoftheAnimationListenerInterface

//filename:ViewAnimationListener.java//project:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationListenerimplementsAnimation.AnimationListener{

publicViewAnimationListener(){}publicvoidonAnimationStart(Animationanimation){Log.d("AnimationExample","onAnimationStart");}publicvoidonAnimationEnd(Animationanimation){Log.d("AnimationExample","onAnimationEnd");}publicvoidonAnimationRepeat(Animationanimation){Log.d("AnimationExample","onAnimationRepeat");}}

TheViewAnimationListenerclassinListing18-23justlogsmessages.CodeinListing18-24showshowtoattachananimationlistenertoanAnimationobject.

Listing18-24.AttachinganAnimationListenertoanAnimationObject

privatevoidanimateListView(){ListViewlv=(ListView)this.findViewById(R.id.list_view_id);

//Initwidth,heightandassumingViewAnimationfromListing18-21ViewAnimationanimation=newViewAnimation(width,height);animation.setAnimationListener(newViewAnimationListener());lv.startAnimation(animation);}

NotesonTransformationMatricesAsyouhaveseeninthischapter,matricesarekeytotransformingviewsandanimations.Let’sexploresomekeymethodsoftheMatrixclass.

Matrix.reset():Resetsamatrixtoanidentitymatrix,whichcausesnochangetotheviewwhenapplied

Matrix.setScale(…args..):Changessize

Matrix.setTranslate(…args..):Changespositiontosimulatemovement

Matrix.setRotate(…args..):Changesorientation

Matrix.setSkew(…args..):Distortsaview

Thelastfourmethodshaveinputparameters.

Youcanmultiplymatricestogethertocompoundtheeffectofindividualtransformations.InListing18-25,considerthreematrices,m1,m2,andm3,whichareidentitymatrices:

Listing18-25.ViewAnimationusingpreTranslateandpostTranslate

m1.setScale(..scaleargs..);m2.setTranslate(..translateargs..)m3.setConcat(m1,m2)

Transformingaviewbym1andthentransformingtheresultingviewwithm2isequivalenttotransformingthesameviewbym3.Notethatm3.setConcat(m1,m2)isdifferentfromm3.setConcat(m2,m1).setConcat(matrix1,matrix2)multipliestwomatricesinthatgivenorder.

YouhavealreadyseenthepatternusedbythepreTranslateandpostTranslatemethodstoaffectmatrixtransformation.Infact,preandpostmethodsarenotuniquetotranslate,andyouhaveversionsofpreandpostforeveryoneofthesettransformationmethods.Ultimately,apreTranslatesuchasm1.preTranslate(m2)isequivalentto

m1.setConcat(m2,m1)

Inasimilarmanner,themethodm1.postTranslate(m2)isequivalentto

m1.setConcat(m1,m2)

ConsiderthecodeinListing18-26

Listing18-26.PreandPostTranslatePattern

matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);

ThecodeinthisListing18-26isequivalenttothecodeinListing18-27

Listing18-27.EquivalenceofPreandPostTranslatePattern

MatrixmatrixPreTranslate=newMatrix();matrixPreTranslate.setTranslate(-centerX,-centerY);

MatrixmatrixPostTranslate=newMatrix();matrixPostTranslate.setTranslate(centerX,centerY);

matrix.setConcat(matrixPreTranslate,matrix);matrix.setConcat(matrix,matrixPostTranslate);

ExploringPropertyAnimations:TheNewAnimationAPITheanimationAPIisoverhauledin3.0and4.0ofAndroid.Thisnewapproachtoanimationsiscalledpropertyanimation.ThepropertyanimationAPIisextensiveanddifferentenoughtorefertothepreviousanimationAPI(priorto3.x)asthelegacyAPIeventhoughthepreviousapproachisstillvalidandnotdeprecated.TheoldanimationAPIisinthepackageandroid.view.animation.ThenewanimationAPIisinthepackageandroid.animation.KeyconceptsinthenewpropertyanimationAPIare:

Animators

Valueanimators

Objectanimators

Animatorsets

Animatorbuilders

Animationlisteners

Propertyvalueholders

Typeevaluators

Viewpropertyanimators

Layouttransitions

AnimatorsdefinedinXMLfiles

Wewillcovermostoftheseconceptsintherestofthechapter.

UnderstandingPropertyAnimationThepropertyanimationapproachchangesthevalueofapropertyovertime.Thispropertycanbeanything,suchasastand-aloneinteger,afloat,oraspecificpropertyofanobjectsuchasaview.Forexample,youcanchangeanintvaluefrom10to200overatimeperiodof5secondsbyusingananimatorclasscalledValueAnimator(seeListing18-28).

Listing18-28.ASimpleValueAnimator

//file:TestBasicValueEvaluator.java(ProAndroid5_ch18_TestPropertyAnimation.zip)//Defineananimatortochangeanintvaluefrom10to200

ValueAnimatoranim=ValueAnimator.ofInt(10,200);

//setthedurationfortheanimationanim.setDuration(5000);//5seconds,default300ms

//Provideacallbacktomonitorthechangingvalueanim.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){publicvoidonAnimationUpdate(ValueAnimatoranimation){Integervalue=(Integer)animation.getAnimatedValue();//thiscodegetscalledmanymanytimesfor5seconds.//Thevaluewillrangefrom10to200}});anim.start();

Theideaiseasytograsp.AValueAnimatorisamechanismtodosomethingevery10ms(thisisthedefaultframerate).Althoughthisisthedefaultframerate,dependingonthesystemloadyoumaynotgetcalledthatmanytimes.Fortheexamplegivenwecouldexpecttogetcalled500timesoveraspanof5seconds.Onanemulatorourtestsshowthatitmaybeasfewas10times.Howeverthelastcallwillbeclosetothe5-secondduration.

Inthecorrespondingcallbackthatiscalledforeveryframe(every10ms),youcanchoosetoupdateavieworanyotheraspecttoaffectanimation.InadditiontotheonAnimationUpdate,otherusefulcallbacksareavailableonthegeneralpurposeAnimator.AnimatorListenerinterface(Listing18-28)fromtheAndroidSDK

whichcanbeattachedtotheValueAnimatorthroughitsbaseclassAnimator.SoonaValueAnimator,youcandoaddListener(Animator.AnimatorListenerlistener).SeeListing18-29.

Listing18-29.AnimatorListenerCallbackInterface

publicstaticinterfaceAnimator.AnimatorListener{abstractvoidonAnimationStart(Animatoranimation);abstractvoidonAnimationRepeat(Animatoranimation);abstractvoidonAnimationCancel(Animatoranimation);abstractvoidonAnimationEnd(Animatoranimation);}

YoucanusethesecallbacksinListing18-29tofurtheractonobjectsofinterestduringorafterananimation.

PropertyAnimationreliesontheavailabilityofanandroid.os.Looperonthethreadthatisinitiatingtheanimation.ThisisgenerallythecasefortheUIthread.ThecallbackshappenontheUIthreadaswellwhentheanimatingthreadisthemainthread.

AsyouuseValueAnimatorsandtheirlisteners,keepinmindthelifetimeoftheseobjects.EvenifyouletthereferenceofaValueAnimatorgofromyourlocalscope,theValueAnimatorwillliveonuntilitfinishestheanimation.IfyouweretoaddalistenerthenallthereferencesthatthelistenerholdsarealsovalidforthelifetimeoftheValueAnimator.

PlanningaTestBedforPropertyAnimationStartingwiththebasicideaofvalueanimators,Androidprovidesanumberofderivedwaystoanimateanyarbitraryobject,especiallyviews.Todemonstratethesemechanisms,wewilltakeasimpletextviewinalinearlayoutandanimateitsalphaproperty(simulatingtransparencyanimation)andalsothexandypositions(simulatingmovement).WewilluseFigure18-5asananchortoexplainpropertyanimationconcepts.

Figure18-5.ActivitytodemonstratePropertyanimations

EachbuttoninFigure18-5usesaseparatemechanismtoanimatethetextviewatthebottomofthefigure.Themechanismswewilldemonstrateareasfollows:

Button1:Usingobjectanimators,fadeoutandfadeinaviewalternativelyattheclickofabutton.

Button2:UsinganAnimatorSet,runafade-outanimationfollowedbyfade-inanimationinasequentialmanner.

Button3:UseanAnimatiorSetBuilderobjecttotiemultipleanimationstogetherina“before,”“after,”or“with”relationship.Usethisapproachtorunthesameanimationasbutton2.

Button4:DefineanXMLfileforbutton2’ssequenceanimationandattachittothetextviewforthesameanimationaffect.

Button5:UsingaPropertyValuesHolderobject,animatemultiplepropertiesofthetextviewinthesameanimation.Wewillchangethexandyvaluestomovethetextviewfrombottomrighttotopleft.

Button6:UseViewPropertyAnimatortomovethetextviewfrombottomrighttotopleft(sameanimationasbutton5).

Button7:UseaTypeEvaluatoroncustompointobjectstomovethetextviewfrombottomrighttotopleft(sameanimationasbutton5).

Button8:Usekeyframestoaffectmovementandalsoalphachangesonthetextview(sameanimationasbutton5,butstaggered).

ConstructingtheactivityinFigure18-5isstraightforward.YoucanseethelayoutforthisactivityandtheactivitycodeinthedownloadprojectfileProAndroid5_ch18_TestPropertyAnimation.zip.Let’sstartwiththefirstbutton.

AnimatingViewswithObjectAnimatorsThefirstbuttoninFigure18-5(Fadeout:Animator)invokesthetoggleAnimation(View)methodthatisinListing18-30.

Listing18-30.BasicViewAnimationwithObjectAnimators

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtoggleAnimation(ViewbtnView){

ButtontButton=(Button)btnView;//Thebuttonwehavepressed//m_tv:isthepointertothetextview//Animatethealphafromcurrentvalueto0thiswillmakeitinvisibleif(m_tv.getAlpha()!=0){ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);fadeOut.setDuration(5000);fadeOut.start();tButton.setText("FadeIn");}//Animatethealphafromcurrentvalueto1thiswillmakeitvisibleelse{ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);fadeIn.setDuration(5000);fadeIn.start();

tButton.setText("Fadeout");}}

ThecodeinListing18-30firstexaminesthealphavalueofthetextview.Ifthisvalueisgreaterthan0,thenthecodeassumesthatthetextviewisvisibleandrunsafade-outanimation.Attheendofthefade-outanimation,thetextviewwillbeinvisible.Ifthealphavalueofthetextviewis0,thenthecodeassumesthetextviewisinvisibleandrunsafade-inanimationtomakethetextviewvisibleagain.

TheObjectAnimatorcodeinListing18-30isreallysimple.AnObjectAnimatorisobtainedonthetextview(m_tv)usingthestaticmethodofFloat().Thefirstargumenttothismethodisanobject(m_tv).ThesecondargumentisthepropertynameoftheobjectthatyouwanttheObjectAnimatortomodifyoranimate.Inthecaseofthetextviewm_tvthispropertynameisalpha.Thetargetobjectneedstohaveapublicmethodtomatchthisname.Forapropertynamedalpha,thecorrespondingviewobjectneedstohavethefollowingsetmethod:

view.setAlpha(floatf);

ThethirdargumenttoofFloat()isthevalueofthepropertyattheendoftheanimation.Ifyouspecifyafourthargument,thenthethirdargumentisthestartingvalueandthefourthisthetargetvalue.Youcanpassmoreargumentsaslongastheyareallfloats.Theanimationwillusethosevaluesasintermediatevaluesintheanimationprocess.

Ifyouspecifyjustthe“to”value,thenthe“from”valueistakenfromthecurrentvaluebyusing

view.getAlpha();

Whenyouplaythisanimation,thetextviewwillgraduallydisappearfirst.ThecodeinListing18-30thenrenamesthebuttonto“Fadein.”Nowifyouclickthebuttonagain,whichisnowcalled“Fadein,”thesecondanimationinListing18-30isrun,andthetextviewwillappeargraduallyoveraperiodof5seconds.

AchievingSequentialAnimationwithAnimatorSetButton2inFigure18-5runstwoanimationsoneaftertheother:afade-outfollowedbyafade-in.Wecoulduseanimationlistenercallbackstowaitforthefirstanimationtofinishandthenstartthesecondanimation.ThereisanautomatedwaytorunanimationsintandemthroughtheclassAnimatorSettogetthesameeffect.Button2demonstratesthisinListing18-31.

Listing18-31.SequentialAnimationThroughanAnimatorSet

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidsequentialAnimation(ViewbView){

ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);

ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);AnimatorSetas=newAnimatorSet();as.playSequentially(fadeOut,fadeIn);as.setDuration(5000);//5secsas.start();}

InListing18-31,wehavecreatedtwoanimators:afade-outanimatorandafade-inanimator.Thenwecreatedananimatorsetandtellittoplaybothanimationssequentially.

YoucanalsochoosetoplayanimationstogetherusingananimatorsetbycallingthemethodplayTogether().Bothofthesemethods,playSequentially()andplayTogether(),cantakeavariablenumberofAnimatorobjects.

Whenyouplaythisanimation,thetextviewwillgraduallydisappearandthenreappear,muchliketheanimationyousawearlier.

SettingAnimationRelationshipswithAnimatorSet.BuilderAnimatorSetalsoprovidesabitmoreelaboratewaytolinkanimationsthroughautilityclasscalledAnimatorSet.Builder.Listing18-32demonstratesthis.

Listing18-32.UsinganAnimatorSetBuilder

//filename:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestAnimationBuilder(Viewv){

ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);AnimatorSetas=newAnimatorSet();//play()returnsthenestedclass:AnimatorSet.Builderas.play(fadeOut).before(fadeIn);as.setDuration(5000);//5secsas.start();}

TheplaymethodonanAnimatorSetreturnsaclasscalledAnimatorSet.Builder.Thisispurelyautilityclass.Themethodsonthisclassareafter(animator),before(animator),andwith(animator).Thisclassisinitializedwiththefirstanimatoryousupplythroughtheplaymethod.Everyothercallonthisobjectiswithrespecttothisoriginalanimator.ConsiderListing18-33:

Listing18-33.UsingAnimationSet.Builder

AnimatorSet.Builderbuilder=someSet.play(main_animator).before(animator1);

Withthiscodeanimator1willplayafterthemain_animator.Whenwesaybuilder.after(animator2),theanimationofanimator2willplaybeforemain_animator.Themethodwith(animator)playstheanimationstogether.

ThekeypointwithanAnimationBuilderisthattherelationshipestablishedviabefore(),after(),andwith()isnotchainedbutonlytiedtotheoriginalanimatorthatwasobtainedfromplay()method.Also,theanimationstart()methodisnotonthebuilderobjectbutontheoriginalanimatorset.WhenyouplaythisanimationthroughButton3,thetextviewwillgraduallydisappearandthenreappear,muchasinthepreviousanimation.

UsingXMLtoLoadAnimatorsItisonlytobeexpectedthattheAndroidSDKallowsanimatorstobedescribedinXMLresourcefiles.AndroidSDKhasanewresourcetypecalledR.animatortodistinguishanimatorresourcefiles.TheseXMLfilesarestoredinthe/res/animatorsubdirectory.Listing18-34isanexampleofananimatorsetdefinedinanXMLfile.

Listing18-34.AnAnimatorXMLResourceFile

<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/animator/fadein.xml(ProAndroid5_ch18_TestPropertyAnimation.zip)--><setxmlns:android="http://schemas.android.com/apk/res/android"

android:ordering="sequentially"><objectAnimatorandroid:interpolator="@android:interpolator/accelerate_cubic"android:valueFrom="1"android:valueTo="0"android:valueType="floatType"android:propertyName="alpha"android:duration="5000"/><objectAnimatorandroid:interpolator="@android:interpolator/accelerate_cubic"android:valueFrom="0"android:valueTo="1"android:valueType="floatType"android:propertyName="alpha"android:duration="5000"/></set>

YouwillnaturallywonderwhatXMLnodesareavailableforyoutodefinetheseanimations.Asof4.0theallowedXMLtagsareasfollows:

animator:BindstoValueAnimator

objectAnimator:BindstoObjectAnimator

set:BindstoAnimatorSet

YoucanseeabasicdiscussionofthesetagsatthefollowingAndroidSDKURL:

http://developer.android.com/guide/topics/graphics/prop-animation.html#declaring-xml

ThecompleteXMLreferencefortheanimationtagscanbefoundatthefollowingURL:

http://developer.android.com/guide/topics/resources/animation-resource.html#Property

OnceyouhavethisXMLfile,youcanplaythisanimationusingthemethodshowninListing18-35.

Listing18-35.LoadinganAnimatorXMLResourceFile

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidsequentialAnimationXML(ViewbView){

AnimatorSetset=(AnimatorSet)AnimatorInflater.loadAnimator(this,R.animator.fadein);set.setTarget(m_tv);set.start();}

NoticehowitisnecessarytoloadtheanimationXMLfilefirstfollowedbyexplicitlysettingtheobjecttoanimate.Inourcase,theobjecttoanimateisthetextviewrepresentedbym_tv.ThemethodinListing18-35iscalledbybutton4(FadeOut/FadeInXML).Whenthisanimationruns,thetextviewwillfadeoutfirstandthenreappearbyfadingin,justasinthepreviousalphaanimations.

UsingPropertyValuesHolderSofar,wehaveseenhowtoanimateasinglevalueinasingleanimation.TheclassPropertyValuesHolderletsusanimatemultiplevaluesduringtheanimationcycle.Listing18-36demonstratestheuseofthePropertyValuesHolderclass.

Listing18-36.UsingthePropertyValueHolderClass

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestPropertiesHolder(Viewv){

//Getthecurrentcoordinatesofthetextview.//Thisallowsustoknowstartingandendingpositionstoanimatefloath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();

//Settheviewtothebottomrightasastartingpointm_tv.setX(w);m_tv.setY(h);

//fromtherightbottomanimate"x"toitsoriginalposition:topleftPropertyValuesHolderpvhX=PropertyValuesHolder.ofFloat("x",x);

//fromtherightbottomanimate"y"toitsoriginalpositionPropertyValuesHolderpvhY=PropertyValuesHolder.ofFloat("y",y);

//whenyoudonotspecifythefromposition,theanimationwilltakethecurrentposition//asthefromposition.

//Telltheobjectanimatortoconsiderboth//"x"and"y"propertiestoanimatetotheirrespectivetargetvalues.ObjectAnimatoroa=ObjectAnimator.ofPropertyValuesHolder(m_tv,pvhX,pvhY);

//setthedurationoa.setDuration(5000);//5secs

//hereisawaytosetaninterpolatoronanyanimatoroa.setInterpolator(newAccelerateDecelerateInterpolator());oa.start();}

APropertyValuesHolderclassholdsapropertynameanditstargetvalue.ThenyoucandefinemanyofthesePropertyValuesHolderswiththeirownpropertytoanimate.YoucansupplythissetofPropertyValuesHolderstotheobjectanimator.Theobjectanimatorwillthensetthesepropertiestotheirrespectivevaluesonthetargetobject.Witheachrefreshoftheanimation,allthevaluesfromeachPropertyValuesHolderwillbeappliedallatonce.Thisismoreefficientthanapplyingmultipleanimationsinparallel.

Button5inFigure18-5runsthecodeinListing18-36.Whenthisanimationruns,thetextviewwillemergefrombottomrightandmigratetowardthetopleftin5seconds.

UnderstandingViewPropertiesAnimationAndroidSDKhasanoptimizedapproachtoanimatevariouspropertiesofviews.ThisisdonethroughaclasscalledViewPropertyAnimator.Listing18-37usesthisclasstomovethetextviewfrombottomrighttotopleft.

Listing18-37.UsingaViewPropertyAnimator

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestViewAnimator(Viewv){

//Remembercurrentboundariesfloath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();

//Positiontheviewatbottomrightm_tv.setX(w);m_tv.setY(h);

//GetaViewPropertyAnimatorfromthetextviewViewPropertyAnimatorvpa=m_tv.animate();

//Setasmanytargetvaluesyouwanttosetvpa.x(x);vpa.y(y);

//Setdurationandinterpolatorsvpa.setDuration(5000);//2secsvpa.setInterpolator(newAccelerateDecelerateInterpolator());

//TheanimationautomaticallystartswhentheUIthreadgetstoit.//Noneedtoexplicitlycallthestartmethod.//vpa.start();}

ThestepstouseViewPropertyAnimatorareasfollows:

1. GetaViewPropertyAnimatorbycallingtheanimate()methodonaview.

2. UsetheViewPropertyAnimatorobjecttosetvariousfinalpropertiesofthatview,suchasx,y,scale,alpha,andsoon.

3. LettheUIthreadproceedbyreturningfromthefunction.Theanimationwillautomaticallystart.

Thisanimationisinvokedbybutton6.Whenthisanimationruns,thetextviewwillmigratefrombottomrighttotopleft.

UnderstandingTypeEvaluatorsAswehaveseen,anobjectanimatordirectlysetsaparticularvalueonatargetobjectwitheachanimationcycle.Thesevaluessofarhavebeensinglepointvaluessuchasfloats,ints,andsoon.Whathappensifyourtargetobjecthasapropertythatisanobjectitself?Thisiswheretypeevaluatorscomeintoplay.

Toillustratethisconsideraviewonwhichwewanttosettwovaluessuchas‘x’and‘y’.Listing18-35showshowweencapsulatearegularviewforwhichweknowhowtochangexandy.TheencapsulationwillallowtheanimationtocallonceforbothxandythroughthePointFabstractionavailableintheAndroidgraphicspackage.WewillprovideasetPoint(PointF)methodandthen,insidethatmethod,parseoutxandyandsetthemontheview.TakealookatListing18-38.

Listing18-38.AnimatingaViewThroughaTypeEvaluator

//file:AnimatableView.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicclassMyAnimatableView{

PointFcurPoint=null;Viewm_v=null;publicMyAnimatableView(Viewv){curPoint=newPointF(v.getX(),v.getY());m_v=v;}publicPointFgetPoint(){returncurPoint;}publicvoidsetPoint(PointFp){curPoint=p;m_v.setX(p.x);m_v.setY(p.y);}}

IncodeListing18-38TypeEvaluatorisahelperobjectthatknowshowtosetacompositevaluesuchasatwo-dimensionalorthree-dimensionalpointduringananimationcycle.Inascenarioinvolvingcompositefields(representedasanobject),anObjectAnimatorwilltakethestartingcompositevalue(likethePointFobjectwhichisacompositeofxandy),anendingcompositevalueandpassthemtoaTypeEvaluatorhelperobjecttogettheintermediateobjectvalue.Thiscompositevalueisthensetonthetargetobject.Listing18-39showshowaTypeEvlautorcalculatesthisintermediatevaluethroughitsevaluatemethod.

Listing18-39.CodingaTypeEvaluator

//file:MyPointEvaluator.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicclassMyPointEvaluatorimplementsTypeEvaluator<PointF>{

publicPointFevaluate(floatfraction,PointFstartValue,PointFendValue){PointFstartPoint=(PointF)startValue;PointFendPoint=(PointF)endValue;returnnewPointF(startPoint.x+fraction*(endPoint.x-startPoint.x),

startPoint.y+fraction*(endPoint.y-startPoint.y));}}

FromListing18-39youcanseethatyouneedtoinheritfromtheTypeEvaluatorinterfaceandimplementtheevaluate()method.Inthismethod,youwillbepassedthefractionoftheanimation’stotalprogress.Youcanusethatfractiontoadjustyourintermediatecompositevalueandreturnitasatypedvalue.

Listing18-40showshowanObjectAnimatorusesMyAnimatableViewandtheMyPointEvaluatortoanimatecompositevaluesforaView.

Listing18-40.UsingaTypeEvaluator

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestTypeEvaluator(Viewv){

floath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();

PointFstartingPoint=newPointF(w,h);PointFendingPoint=newPointF(x,y);

//m_atv:Youwillneedthiscodeinyouractivityearlierasalocalvariable:MyAnimatableViewm_atv=newMyAnimatableView(m_tv);

ObjectAnimatorviewCompositeValueAnimator=ObjectAnimator.ofObject(m_atv,"point",newMyPointEvaluator(),startingPoint,endingPoint);

viewCompositeValueAnimator.setDuration(5000);viewCompositeValueAnimator.start();}

NoticeinListing18-40thattheObjectAnimatorisusingthemethodofObject()asopposedtoofFloat()orofInt().AlsonoticethatthestartingvalueandendingvaluefortheanimationarecompositevaluesrepresentedbytheclassPointF.ThegoaloftheobjectanimatorisnowtocomeupwithanintermediatevalueforPointFandthenpassittothemethodsetPoint(PointF)onthecustomclassMyAnimatableView.TheclassMyAnimatableViewcanaccordinglysettherespectiveindividualpropertiesonthecontainedtextview.ThisanimationinListing18-40usingtheTypeEvaluatorisinvokedbybutton7.Whenthisanimationruns,theviewwillmigratefrombottomrighttotopleft.

UnderstandingKeyFrames

Keyframesareusefulplacesduringananimationcycletoputkeytimemarkers(significantinstancesintime).Akeyframespecifiesaparticularvalueforapropertyatagivenmomentintime.Thekeymarker’stimeisbetween0(beginningofanimation)and1(endofanimation).Onceyougatherthesekey-framevalues,yousetthemagainstaparticularpropertysuchasalpha,x,ory.ThisassociationofkeyframestotheirrespectivepropertiesisdonethroughthePropertyValuesHolderclass.YouthentelltheObjectAnimatortoanimatetheresultingPropertyValuesHolder.Listing18-41demonstrateskey-frameanimation.

Listing18-41.AnimatingaViewUsingKeyFrames

//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestKeyFrames(Viewv){

floath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();

//Startframe:0.2,alpha:0.8Keyframekf0=Keyframe.ofFloat(0.2f,0.8f);

//Middleframe:0.5,alpha:0.2Keyframekf1=Keyframe.ofFloat(.5f,0.2f);

//endframe:0.8,alpha:0.8Keyframekf2=Keyframe.ofFloat(0.8f,0.8f);

PropertyValuesHolderpvhAlpha=PropertyValuesHolder.ofKeyframe("alpha",kf0,kf1,kf2);PropertyValuesHolderpvhX=PropertyValuesHolder.ofFloat("x",w,x);

//endframeObjectAnimatoranim=ObjectAnimator.ofPropertyValuesHolder(m_tv,pvhAlpha,pvhX);anim.setDuration(5000);anim.start();}

TheanimationinListing18-41isinvokedbybutton8.Whenthisanimationruns,youwillseethetextmovefromrighttoleft.When20%ofthetimehaspassed,alphawillchangeto80%.Thealphavaluewillreach20%athalfwayandchangebackto80%atthe80thpercentileoftheanimationtime.

UnderstandingLayoutTransitionsThepropertyanimationAPIalsoprovideslayout-basedanimationsthroughtheLayoutTransitionclass.ThisclassiswelldocumentedaspartofthestandardAPI

JavadocatthefollowingURL.

http://developer.android.com/reference/android/animation/LayoutTransition.html

Wewillsummarizehereonlythekeypointsoflayouttransitions.Toenablelayouttransitionsonaviewgroup(mostlayoutsareviewgroups),youwillneedtousethecodeshowninListing18-42.

Listing18-42.SettingaLayoutTransition

viewgroup.setLayoutTransition(newLayoutTransition());

WiththecodeinListing18-42thelayoutcontainer(ViewGroup)willexhibitdefaulttransitionsasviewsareaddedandremoved.ALayoutTransitionobjecthasfourdifferentdefaultanimationsthatcovereachofthefollowingscenarios:

Addaview(animationfortheviewthatisappearingduetoanaddorashow)

Changeappearing(animationfortherestoftheitemsinthelayoutastheycouldchangetheirsizeorappearanceduetoanewitembeingadded)

Removeaview(animationfortheviewthatisdisappearingduetoaremoveorahide)

Changedisappearing(animationfortherestoftheitemsinthelayoutastheycouldtheirsizeorappearanceduetoanitembeingremoved)

Ifyouwantcustomanimatorsforeachofthesecases,youcansetthemontheLayoutTransitionobject.HereisanexampleinListing18-43.

Listing18-43.LayoutTransitionMethods

//HereishowyougetanewlayouttransitionLayoutTransitionlt=newLayoutTransition();

//YoucansetthislayouttransitiononalayoutsomeLayout.setLayoutTransition(lt);

//obtainadefaultanimatorifyouneedtorememberAnimatordefaultAppearAnimator=lt.getAnimator(APPEARING);

//createanewanimatorObjectAnimatorsomeNewObjectAnimator1,someOtherObjectAnimator2;

//setitasyourcustomanimatorfortheallowedsetofanimators

lt.setAnimator(APPEARING,someNewObjectAnimator1);lt.setAnimator(CHANGE_APPEARING,someNewObjectAnimator1);lt.setAnimator(DISAPPEARING,someNewObjectAnimator1);lt.setAnimator(CHANGE_DISAPPEARING,someOtherObjectAnimator2);

Becausetheanimatoryousupplytoalayouttransitionappliestoeachview,theanimatorsareinternallyclonedbeforebeingappliedtoeachview.

ResourcesHerearesomeusefullinkstowhenyouareworkingwiththeAndroidAnimationAPI:

http://www.androidbook.com/item/3901:AuthorresearchnotesonAndroidpropertyanimations.

http://android-developers.blogspot.com/2011/02/animation-in-honeycomb.html:Akeyblogonpropertyanimations.

http://android-developers.blogspot.com/2011/05/introducing-viewpropertyanimator.html:Ablogonviewpropertyanimations.

http://developer.android.com/guide/topics/graphics/prop-animation.html:PrimarydocumentationonpropertyanimationsfromtheAndroidSDK.

http://developer.android.com/guide/topics/graphics/animation.htmlAndroiddocumentationlinkstoallanimationtypes,includingpropertyanimationsandold-styleanimations.

http://developer.android.com/reference/android/view/animation/package-summary.html:JavadocAPIfortheolderanimationpackageandroid.view.animation.

http://developer.android.com/guide/topics/resources/animation-resource.html:XMLtagsforvariousanimationtypes.

http://www.androidbook.com/proandroid5/projects:Downloadabletestprojectsforthischapter.ThenamesofthezipfilesareProAndroid5_ch18_TestFrameAnimation.zip,ProAndroid5_ch18_TestLayoutAnimation.zip,ProAndroid5_ch18_TestViewAnimation.zip,andProAndroid5_ch18_TestPropertyAnimation.zip.

SummaryInthischapterwehavecoveredframe-by-frameanimation,layoutanimation,viewanimation,interpolators,transformationmatrices,Camera,andvariouswaysofusingthenewpropertyanimationAPI.Allconceptsarepresentedwithworkingcodesnippetsandsupportedbyworkingdownloadableprojects.

Chapter19

ExploringMapsandLocation-BasedServicesInthischapter,wearegoingtotalkaboutmapsandlocation-basedservices.Location-basedservicesformoneofthemoreexcitingpiecesoftheAndroidSDK.ThisportionoftheSDKprovidesAPIstoletapplicationdevelopersdisplayandmanipulatemaps,obtainreal-timedevice-locationinformation,andtakeadvantageofotherexcitingfeatures.WorkingwithmapschangedsignificantlywhenGoogleintroducedtheMapFragmentandversion2oftheGoogleMapsAPI.Thischapterwillgointodetailsofthenewwaysofcreatingmapsandmanipulatingthem.

Thelocation-basedservicesfacilityinAndroidsitsontwopillars:themappingandlocation-basedAPIs.ThemappingAPIsinAndroidprovidefacilitiesforyoutodisplayamapandmanipulateit.Forexample,youcanzoomandpan;youcanchangethemapmode(fromsatelliteviewtotrafficview,forexample);youcanaddmarkersandcustomdatatothemap;andsoon.TheotherendofthespectrumisGlobalPositioningSystem(GPS)dataandinformationaboutlocations,bothofwhicharehandledbythelocationpackage.

TheseAPIsoftenreachacrosstheInternettoinvokeservicesfromGoogleservers,viaGooglePlayServices(thelocaluberapplicationonthedevice).Therefore,youwillusuallyneedtohaveInternetconnectivityforthesetowork.Inaddition,GooglehasTermsofServicethatyoumustagreetobeforeyoucandevelopapplicationswiththeseGoogleservices.Readthetermscarefully;Googleplacessomerestrictionsonwhatyoucandowiththeservicedata.Forexample,youcanuselocationinformationforusers’personaluse,butcertaincommercialusesarerestricted,asareapplicationsinvolvingautomatedcontrolofvehicles.ThetermswillbepresentedtoyouwhenyousignupforaMapsAPIkey.

Inthischapter,we’llgothrougheachofthesepackages.We’llstartwiththemappingAPIsandshowyouhowtousemapswithyourapplications.Asyou’llsee,mappinginAndroidboilsdowntousingMapFragmentclassinadditiontothemappingAPIs,whichintegratewithGoogleMaps.Wewillalsoshowyouhowtoplacecustomdataontothemapsthatyoudisplayandhowtoshowthecurrentlocationofthedeviceonamap.Aftertalkingaboutmaps,we’lldelveintolocation-basedservices,whichextendthemappingconcepts.WewillshowyouhowtousetheAndroidGeocoderclassandtheLocationServicesservice.WewillalsotouchonthreadingissuesthatsurfacewhenyouusetheseAPIs.

UnderstandingtheMappingPackageAswementioned,themappingAPIsareoneofthecomponentsofAndroid’slocation-

basedservices.Themappingpackagecontainsalmosteverythingyou’llneedtodisplayamaponthescreen,handleuserinteractionwiththemap(suchaszooming),displaycustomdataontopofthemap,andsoon.IntheoldversionofAndroidMaps,yourapplicationwouldtalkdirectlytotheGoogleMapsservicesforeverythingmap-related.Inthenewversion,yourapplicationmusttalktoGooglePlayServices,whichisalocalapponthedevice,providedaspartoftheoperatingsystem.YourappstillalsomakescallsovertheInternetfordata,butifGooglePlayServicesisnotpresentlocallyonthedevice,yourmapswillnotwork.Ifyouneedmapsondevicesthatdon’thaveGooglePlayServicesyou’llneedtoexploreoneoftheothermapspackagesavailableforAndroid(e.g.,MapQuest).

InorderforyourapplicationtotalktoGooglePlayServices,youwillneedtoincludetheGooglePlayServiceslibraryintoyourapplication.AndroidStudiodoesthisdifferentlythanEclipsewithADT.SeetheReferencessectionbelowforalinktoonlineinstructionsforthelatestwaytodothis.BeforeyouincludetheGooglePlayServiceslibraryinyourapplication,youmustfirstdownloaditthroughtheSDKManager.You’llfinditunderExtras.

YoumayhavenoticedthatyourAndroidSDKManagershowsGoogleAPIpackagesinadditiontotheAndroidSDKplatforms.Previously,youhadtobaseyourapplicationonaGoogleAPIspackageinordertousemaps,butthatisnolongertrue.Instead,theMapsAPIintegratestoGooglePlayServices,soyourapplicationcanbebasedonaregularAndroidpackage.However,totestamaps-basedappintheemulator,youwouldneedtobaseyouremulator’sAndroidVirtualDevice(AVD)onaGoogleAPIspackage.Moreontestingappslater.

Thefirststeptoworkingwiththemapspackageistodisplayamap.Todothat,you’lluseMapFragment(orSupportMapFragmentifyouwantbackwardscompatibilitywithversionsofAndroidpriortoAPI12,a.k.a.Honeycomb3.1).Usingthisclass,however,requiressomepreparationwork.Specifically,beforeyoucanuseGoogleMapsservices,you’llneedtogetaMapsAPIkeyfromGoogle.TheMapsAPIkeyenablesAndroidtointeractwithGoogleMapsservicestoobtainmapdata.ThenextsectionexplainshowtoobtainaMapsAPIkey.

ObtainingaMapsAPIKeyfromGoogleGooglewantstobeabletoidentifytheapplicationthatisconnectingtothemapservices.Itusesacombinationoftheapplicationpackageandthecertificateusedtosigntheapplication,togenerateaMapsAPIkeythattheapplicationmustthenusetorequestservice.TheMapsAPIkeycanbeusedacrossanumberofpairsofpackagesandcertificates.ThismeansyoucanusethesameMapsAPIkeyfordevelopmentandproduction;thepackagewouldbethesamebutthecertificatesareprobablydifferent.Intheoryyoucouldusethesamekeyacrossmultipleapplicationsbutthispracticeisdiscouraged.Youdon’twanttodothisanywaysinceGoogleimposescertainlimitsontheMapsAPIusageandbysharingaMapsAPIkeywithmultipleapplicationsyoucouldmoreeasilyexceedthelimit.

ToobtainaMapsAPIkey,youneedthecertificatethatyou’llusetosignyourapplication

(inthecaseofadevelopmentversionofyourapp,thedebugcertificate).You’llgettheSHA-1fingerprintofyourcertificate,andthenyou’llenterit,alongwithyourapplication’spackage,onGoogle’swebsitetogenerateanassociatedMapsAPIkey.

First,youmustlocateyourdebugcertificate,whichisgeneratedandmaintainedbyEclipse.YoucanfindtheexactlocationusingtheEclipseIDE.Ifyou’reusinganIDEotherthanEclipse,youjustneedtolocatethekeystorefilewherethecertificatesareheld.FromEclipse’sPreferencesmenu,gotoAndroid Build.Thedebugcertificate’slocationwillbedisplayedintheDefaultDebugKeystorefield,asshowninFigure19-1.

Figure19-1.Thedebugcertificate’slocation

ToextracttheSHA-1fingerprint,youcanrunthekeytoolwiththe–listoption,asshownhere:

keytool-list-aliasandroiddebugkey-keystore"FULLPATHOFYOURdebug.keystoreFILE"-storepassandroid-keypassandroid

Notethatthealiasyouwantfromthedebugstoreisandroiddebugkey.Similarly,thekeystorepasswordisandroid,andtheprivatekeypasswordisalsoandroid.Whenyourunthiscommand,thekeytoolprovidesthefingerprint(seeFigure19-2).

Figure19-2.Thekeytooloutputforthelistoption

You’llnoticethatthefingerprintdisplayedbythekeytoolcommandisthesameasdisplayedinthePreferencesscreenasshowninFigure19-1soyoucouldhavejustgottenitfromthatscreen.ButnowyouknowbothwaystoextractouttheSHA-1fingerprintforyourapplication.WhenyouusekeytooltoextractouttheSHA-1fingerprintfortheproductioncertificate,you’llusethekeystorefile,alias,andpasswordthatyousetupforyourproductioncertificate.

ThenextstepistogotoGoogle’sDeveloperConsoletoaddyourapplication,andfollowingthatyouenabletheMapsAPI.TheresultwillbeyourMapsAPIkeytoincludeinyourapplication.TheDeveloperConsoleishere,andyouwillneedaGoogleaccountinordertogetthere:

https://console.developers.google.com

Youwillneedtocreateanewproject.Aspartofcreatingthenewproject,youneedtoprovideaProjectNameandaProjectID.TheProjectIDwillbepre-populatedwithsomethingstrange-looking.Youcanputanyvalueyouwanthereaslongasitisunique.However,theProjectIDisjustforusebytheGoogleDeveloperConsole;ithasnothingtodowiththesourcecodeofyourapplication.Rememberthatyou’recreatingasampleprojectbasedonthecodeofthischapter’ssampleproject,sothatyoucangetaMapsAPIkeytoseeitwork.

ReadthroughtheTermsofService.Ifyouagreetotheterms,clickCreatetocreateyournewproject.ThissetsupabasictemplateofaprojectwithGoogle.Nextyou’regoingtoenabletheAPIsyouwant.Foramapsapplication,you’llchooseGoogleMapsAndroidAPIv2.Forthechapter’ssampleapplication,youalsowanttoincludetheGeocodingAPI.Youmightgetapop-upwindowcalled‘ConfigureAndroidKeyfor<yourappname>’.Ifyoudon’tgetapop-up,youcannavigatetotheAPIs&auth CredentialssectionofyourprojectintheDevelopersConsoleandgenerateanAPIkeythere.ThisiswhereyouneedtocopyandpasteinboththeSHA-1fingerprintoftheapplicationsigningcertificate,andthepackagenameoftheapplication,separatedbyasemicolon.Thepackagenameistheonefromyoursourcecode.Notethatyoucancopyinmorethanoneline,soifyouhavetheSHA-1fingerprintfromtheproductionapplicationsigningcertificate(whichistypicallydifferentfromtheandroiddebugkeyusedindevelopment),youcouldaddasecondlinefortheproductionapplication.

OnceyoupresstheCreatebuttononthisscreenyouwillgetanAPIkey.ThisiswhatyouwillincludeintheAndroidManifest.xmlfileofyourapplication.TheAPIkeyisactiveimmediately,soyoucanstartusingittoobtainmapdatafromGoogle.

AddingtheMapsAPIKeytoYourApplication

ToseehowtheMapsAPIkeyisaddedtothemanifestfile,seethebottomofListing19-1.

Listing19-1.AndroidManifest.xmlforaSimpleMapsApplication

<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.maps.whereami"android:versionCode="1"android:versionName="1.0">

<uses-sdkandroid:minSdkVersion="10"android:targetSdkVersion="19"/><uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-featureandroid:glEsVersion="0x00020000"android:required="true"/>

<applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><activityandroid:name="com.androidbook.maps.whereami.MainActivity"android:label="@string/app_name"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity><meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version"/><meta-dataandroid:name="com.google.android.maps.v2.API_KEY"

android:value="AIzaSyBDs1ZQgu9X2A4TG1a7fPl-Ge_MKlyviKM"/></application></manifest>

Asyounodoubtnoticed,thereareotherelementswithinthemanifestfilethatmustbepresentforamapsapplicationtowork.The<meta-data>tagabovetheMapsAPIkeyisrequired,asarethepermissionsnearthetop.Technically,theACCESS_FINE_LOCATIONpermissionisnotneededtoshowmaps;itistheresolocationfunctionality(e.g.,GPS)willwork.GPSiscommonlyusedinmapsapplications.ACCESS_NETWORK_STATEandINTERNETpermissionsaretheresothemapsapplicationcandownloadmaptiledata(i.e.,themapgraphics)andtoknowwhattypeandstateofnetworkconnectiontheapplicationhas.TheWRITE_EXTERNAL_STORAGEpermissionistheresothemapsapplicationcancreatealocalcacheofmaptilefilesonthedevice’slocalstoragespace.Withoutcaching,amapsapplicationwouldlikelyspendalotoftimedownloadingmaptilesoverandoveragain,whichisnotonlyinefficientforyourapp,butitplacesanunwantedburdenontheGoogleserversanditcouldconsumealargeportionoftheuser’sdataplan.Andfinally,theglEsVersionfeatureispresentbecauserenderingmapsonthescreenusesOpenGL,sobyrequiringthefeature,theapplicationavoidsgettinginstalledondevicesthatcouldnotdisplaymaps.

Now,let’sstartplayingwithmaps.

UnderstandingMapFragmentThefoundationalbuildingblockofamapapplicationistheMapFragment.ThiswasintroducedinHoneycomb(Android3.1)andreplacedMapViewandMapActivityfunctionality.NowyoucanembedaMapFragmentinsideofaregularAndroidactivity.IfyouwantyourapplicationtorunondevicesrunninganolderversionofAndroid,youcanuseSupportMapFragmentandembeditinsideaFragmentActivity.TheMapFragmentcontainsthemapviewtodisplaymaps,ithandlesusergesturestomanipulatethemap,anditmanagesthebackgroundthreadsthattalktotheGoogleservicestoretrievemapdata.

MapFragmentisaverynicebundleoffunctionality,butit’snotallthatyouneedonyourdevicetomakemapswork.Fortunately,theintegrationwithGooglePlayServicesisallhandledforyou;allyouneedtodoismakeaspecialentryintoyourapp’sAndroidManifest.xmlfile,whichyousawintheprevioussection.

Thefirstsampleapplicationforthischapterwillsimplyshowamaptotheuserandlettheuserexplorethemap.

NoteNoteWegiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.AlsonotethatifyouwanttotestthesesampleswithanAndroidemulator,makesuretheAndroidVirtualDevice(AVD)isbuiltwiththeGoogleAPIs.

PleaserefertothesampleprojectcalledWhereAmI.TheapplicationismadeupofaverybasicFragmentActivity,averysimplelayout,andaSupportMapFragment.Thesampleisusingthecompatibilityclasses,whichmeansitwillrunonGingerbreaddevicesaswellasthelatestmodels.IfyourapponlyneedstorunondevicesnewerthanHoneycomb3.0,youcouldusearegularactivityandaMapFragmentinstead.

Listing19-2showstheactivity.Allthat’sneededistosetupthelayoutand,ifneeded,createtheMapFragmentandinsertitintothelayout’scontainer(aFrameLayout).

Listing19-2.ABasicFragmentActivityforDisplayingaMap

publicclassMainActivityextendsFragmentActivity{privatestaticfinalStringMAPFRAGTAG="MAPFRAGTAG";privateMyMapFragmentmyMapFrag;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();}}}

Iftheactivityisre-createdduetoanorientationchangeforexample,themapfragmentwillstillbeavailableandbeautomaticallyattachedtothenewactivitybyAndroid.Ifthemapfragmentisnotfound,itmeansthisisthefirsttimein,orthemapfragmenthasbeendestroyed,socreateanewoneandattachit.Itdoesn’tgetanyeasierthanthat.ThelayoutsourceisshowninListing19-3.ItissimplyaFrameLayoutwithanidof“container”thatfillstheavailablescreenspace.

Listing19-3.LayoutforSimpleMapDisplay(activity_main.xml)

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.androidbook.maps.whereami.MainActivity"tools:ignore="MergeRootFrame"/>

Ifyouareincludingthemapfragmentwithotheritemsinyouruserinterface,youcansimplyusetheFrameLayoutwhereyouwantthemapfragmenttoappear,embeddedwithinotherlayouts.TheonlycoderemainingisthatoftheMapFragment,whichisshowninListing19-4.Figure19-3showswhattheusersees.

Listing19-4.CodefortheMapFragment

publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{privateGoogleMapmMap=null;

publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}

@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}

@OverridepublicvoidonResume(){super.onResume();doWhenMapIsReady();}

@OverridepublicvoidonPause(){super.onPause();if(mMap!=null)mMap.setMyLocationEnabled(false);}

@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;doWhenMapIsReady();}

/*Wehavearaceconditionwherethefragmentcouldresume*beforeorafterthemapisready.Soweputallourlogic*forinitializingthemapintoacommonmethodthatis*calledwhenthefragmentisresumedorresumingandthe*mapisready.*/voiddoWhenMapIsReady(){if(mMap!=null&&isResumed())mMap.setMyLocationEnabled(true);}}

Figure19-3.AbasicMapFragmentdisplayingyourlocation

Arecentdevelopment(Dec2014)totheMapsAPIistheuseofacallbacktolettheapplicationknowwhenthemapisreadytobeactedupon.ThecallbackissetupusinggetMapAsync(),andtheonMapReady()callbackiscalledwhenthemapcanbe

usedbytheapplication.InbetweencallinggetMapAsync()andinvokingtheonMapReady()callback,Androidissettingupcommunications,threads,etc.,forthemap.ThismeansthatthemapmayormaynotbereadywhenonResume()isinvoked,whichtellsthefragmentthattheUIisnowbeingshown.Therefore,theapplicationneedsaseparatemethodtoworkonthemapanditneedstobecalledbothbyonResume()andbyonMapReady().Forthissampleapplication,thedoWhenMapIsReady()methodfillsthatrole.Theapplicationwantstoshowtheuserthedevice’scurrentlocation,sothesetMyLocationEnabled()methodiscalledindoWhenMapIsReady().ButdoWhenMapIsReady()needstocheckthatthemapexistsandthatthefragmentisresumingorhasresumed.Wedon’tknowwhichwilloccurfirst,butbothmustbetruebeforeweenablelocationupdates.Thecurrentlocationupdatesaredisabledwhenthefragmentgoesoutofview(seeonPause()).TheonlyothercodelinetonoticeisthesetRetainInstance()methodcall.Sincethemapdoesnotneedtobedestroyedandre-createdforaconfigurationchangeoftheactivity,itmakessensetokeepthefragmentandreuseit,alongwiththethreadsandtilesandsoon.YoushouldrememberthataconfigurationchangewillcauseonPause()andonResume()tobeinvokedduringtheconfigchange.Thiswillcorrectlydisablelocationupdatesandre-enablethemduringonResume().

MapControls:MyLocation,Zoom,PanThereareacoupleofartifactsontheuserinterfacetonotice.FirstistheMyLocationbuttonintheupper-rightcorner.Whenyoufirststartthesampleappyouwillseeaveryhigh-levelviewoftheworld.Toshowthecurrentlocation,taptheMyLocationbutton.Thiswillrelocatethemaptothecurrentpositionandzoomin.Secondisthebluedot.Thebluedotrepresentswheretheappthinksyouare,andthecirclerepresentshowaccurateitthinksthislocationis.Thecirclemaygroworshrinkasthelocationinformationchanges.

Theusercanusepinchgestures(i.e.,squeezingtwofingersapartortogether)tozoominorout.Therearemoregesturesthattheusercandoonthemap.Byswiping,theusercanpanthemap;thatis,theycanmovethemaptoseenearbyareas.Usingtwofingersandarotationmove,theusercanrotatethemap.That’salotoffunctionalitythat’sautomaticfromsimplycreatingaMapFragment.

ThesemapcontrolsandmorearecontainedinanobjectoftheUiSettingsclass.Youcangetthemap’sUiSettingsbycallinggetUiSettings()ontheGoogleMapobject(i.e.,mMapinthesampleapp).Youcanthenmodifythesettingsprogrammatically.Forexample,youcouldenableacompasstobedisplayedonthemap,oryoucouldenable/disablethezoomplus/minuscontrolsoitisorisnotdisplayed.Thezoomplus/minuscontrolappearsinthelower-rightcornerandallowstheusertozoominoroutbytappingtheplusorminusbutton,respectively.

MapTypesThedefaultmaptypeisMAP_TYPE_NORMAL.ThisisthetypethatwasusedinFigure

19-3.Itshowstheroadswiththebasicfeaturesofthelandsuchaswherewateris,wheregreenspaceis,andsomeplacesandbuildings.MAP_TYPE_SATELLITEshowsaphotographicsatelliteviewoftheground,sotheuserisabletoseeactualbuildings,cars,andevenpeople.MAP_TYPE_HYBRIDisacombinationofthesetwo;MAP_TYPE_TERRAINislikeanormalmapbutwithtopographicalfeaturesaddedsuchasmountainsandcanyons.ToreallyseetheeffectofMAP_TYPE_TERRAIN,zoominonaplacelikeBoulder,ColoradowithamapsettoTerrain.

YouusethesetMapType()methodofaGoogleMaptochangethetype.

AddingaTrafficLayerInthepreviousversionofAndroidMaps,trafficwastreatedlikethesatelliteandnormalmodesofmaps.WithAPIv2,trafficisenabledseparatelyusingthesetTrafficEnabled()methodofaGoogleMap.

MapTilesIt’shelpfultounderstandwhat’sgoingonwhenyourappdisplaysamap.Googlehascreatedmillionsofbasemaptilestorepresenttheearth’ssurface.Atthelowestzoomlevel(i.e.,zero)thereisonetiletoshowtheentireworld.Atzoomlevel1,therearefourtilesina2x2configuration.Atzoomlevel2,thereare16tilesina4x4configuration.Andsoonuptozoomlevel21.Dependingonwhatpartoftheworldyouwanttodisplay,andwhatthezoomlevelis,theGoogleMapobjectwillfetchandcachetheappropriatetiles.Pantotheside,andanyadditionaltileswillbefetchedanddisplayed.Panbacktowhereyouwereandyourappcanretrievemaptilesfromthecacheinsteadofmakingmoreroundtripstotheserver.

It’sinterestingtonotethatbasemaptilesforthenormaltypeofmapsarenotimages.Googlehasfiguredoutacompressedwaytodescribetheshapesandcolorswithinthetilesinsteadofjustsendingdownimagesforeachtile.Asaresult,normalmaptilesareveryefficientintermsofcachespaceaswellasnetworkbandwidth.Satellitetilesontheotherhandarenotascompressed,sincetheyareimages.

Nowyoucanunderstandwhysometimesamapsapplicationwillshowagraygridpatternandseemtofunctionbutwon’tshowstreetsandotheritems.TheGoogleMapobjecthasbeeninstantiated,anditknowsazoomlevelandwhereitissupposedtobedisplayingamap,butitisunabletoretrieveandrendertilestotheuser.ThisismostoftenduetoaninvalidMapsAPIkey,ortheAPIkeyhasnotbeensetupproperly.ButitcouldalsomeandifficultyinreachingtheGoogleMapsservers.However,ifmaptileshavebeencached,thosetilescanberenderedtotheuserevenifthetileserversatGoogleareunreachable.Therearetwounfortunatethingsaboutmaptilecaching.ThefirstisthattherearenoAPIcallstomanagethemaptilecache,eithertoforcemaptilestobecached,ortochangethesizeofthecache,ortoevicttilesfromthecache.YoujusthavetotrustthatGooglewilldotherightthing.Thesecondisthatmaptilesarecachedperapplication.SojustbecausetheGoogleMapsapplicationmayhavecachedtiles,yourapplicationdoesnothaveaccesstothosetiles.Yourapplicationcanonlyseethecachedtilesthatithascached.

AddingMarkerstoMapsUsuallyyou’llwanttoidentifypointsofinterestonamap,andthisisdoneusingMarkers.Thepointscouldbestationaryobjectslikeaddresses,landmarks,oraparkingspot.Buttheycouldalsobemovingobjectslikecars,planes,people,pets,storms,etc.Yougettochoosewhatthemarkerlookslikeandwhereitispositionedonamap.Andyoucanhavelotsofmarkersallatthesametime.We’regoingtomodifythesampleprogramfromabovetoincludeacoupleofmarkers.You’llseehowtoplacethem,andthenhowtomanipulatetheviewtomakesuretheuserseesthemarkers.

NowusethesampleprogramcalledWhereAmIMarkers.YouwillneedtomodifytheAndroidManifest.xmlfileasbeforetouseyourMapsAPIkey.ThesourcecodeforMyMapFragment.javahasbeenmodifiedasshowninListing19-5.ThescreenwillappearsimilartoFigure19-4.

Listing19-5.CodefortheMapFragmentShowingMarkers

publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{

publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}

@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}

@OverridepublicvoidonMapReady(GoogleMapmyMap){LatLngdisneyMagicKingdom=newLatLng(28.418971,-81.581436);LatLngdisneySevenLagoon=newLatLng(28.410067,-81.583699);

//AddamarkerMarkerOptionsmarkerOpt=newMarkerOptions().draggable(false)

.flat(false).position(disneyMagicKingdom).title("MagicKingdom").icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));myMap.addMarker(markerOpt);

markerOpt.position(disneySevenLagoon).title("SevenSeasLagoon");myMap.addMarker(markerOpt);

//DeriveaboundingboxaroundthemarkersLatLngBoundslatLngBox=LatLngBounds.builder().include(disneyMagicKingdom).include(disneySevenLagoon).build();

//MovethecameratozoominonourlocationsmyMap.moveCamera(CameraUpdateFactory.newLatLngBounds(latLngBox,200,200,0));}}

Figure19-4.Markersonamap

Onceagain,everythingstartsfromacquiringtheGoogleMapobjectfromtheMapFragment.Oncethemapisavailable,youcancreatemarkerswhichinthiscasearebasedonacoupleoffixedLatLngobjects.You’llnoticethoughthatyoudon’tdirectlyinstantiateaMarkerobject.Instead,youuseaMarkerOptionsobjecttospecifyhowthemarkershouldbecreated.ItiswithintheMarkerOptionsobjectthatyoudecidetheposition,title,markershape,color,etc.WhileyoucouldinstantiateaMarkerobjectandthencalleachsetterthatyouwant,MarkerOptionsmakesthingsmucheasier,especiallyifyouneedtocreatemultiplemarkersthatwillsharecommonfeatures.ThissampleonlyusessomeoftheMarkerOptionsfeatures;pleaseseethereferencedocumentationtolearnalloftheoptionsavailable.

Thenextthingyoulikelywanttodoisshowthemaptotheusersuchthatallthemarkersarevisibleatthesametime.Thisrequirestwothings:centeringthemapinthemiddleofthemarkers,andsettingthezoomlevelashighaspossiblewithoutbeingsoclosethatyoucan’tfitallthemarkersintotheview.Fortunately,ahelperclassisavailableforthispurpose.TheLatLngBoundsobjectiscreatedbypassingitalloftheLatLngpointsthatshouldbewithintheview,anditcalculatesthesmallestboxthatcontainsthemall.Inthissample,bothpointsarepassedinatonce.Youcouldalsousealooptopassinallthe

pointsandtheninvokethebuild()methodtoreturntheboundingbox.

Onceyouhaveaboundingbox,youneedtoadjustthemap’scamera.IntheoldversionofGoogleMaps,therewasonlyastraight-downviewofamap,asifyouwereabovethemaplookingstraightdown.WithMapsAPIversion2,thereistheconceptofacamerathatcanlookstraightdown,butcanalsolookatanangle.Ifyouusetwofingersatthesametimeandswipethescreenfromtoptobottom,youwillseetheviewinganglechange.Youhaveineffectpivotedthecamerasoyouarenolongerlookingstraightdown.Thecameracanalsolooktotheeast,southoranyotherdirectionwhenitisangled.Youcantwisttwofingerstorotatethemaptoo.

Allthesecameraangles,zoomlevelsandsoonarecontrolledusingthemap’sanimateCamera()ormoveCamera()methods.ThesemethodstakeaCameraUpdateobjectasinstructions,andtheCameraUpdateFactoryclassgeneratesthose.Inthesample,theboundingboxispassedtotheCameraUpdateFactoryanditreturnsanappropriateCameraUpdatesothatthecamerawillbepositionedinthebestplacetoseeallthemarkers.ThereareseveralothermethodstoCameraUpdateFactorytoaccommodateotherwaysofpositioningthecamera.YoudocansimplezoomIn()andzoomOut()forexample.YoucanalsocreateaCameraPositionobjectandusethat.

Allinall,you’llagreethatplacingmarkersonamapcouldn’tbeeasier.Orcouldit?Wedon’thaveadatabaseoflatitude/longitudepairs,butwe’reguessingthatwe’llneedtosomehowcreateoneormoreLatLngobjectsusingarealaddress.That’swhenyoucanusetheGeocoderclass,whichispartofthelocationpackagethatwe’lldiscussnext.

UnderstandingtheLocationPackageTheandroid.locationpackageprovidesfacilitiesforlocation-basedservices.Inthissection,wearegoingtodiscusstwoimportantpiecesofthispackage:theGeocoderclassandtheLocationManagerservice.We’llstartwithGeocoder.

GeocodingwithAndroidIfyouaregoingtodoanythingpracticalwithmaps,you’lllikelyhavetoconvertanaddress(orlocation)toalatitude/longitudepair.Thisconceptisknownasgeocoding,andtheandroid.location.Geocoderclassprovidesthisfacility.Infact,theGeocoderclassprovidesbothforwardandbackwardconversion—itcantakeanaddressandreturnalatitude/longitudepair,anditcantranslatealatitude/longitudepairintoalistofaddresses.Theclassprovidesthefollowingmethods:

List<Address>getFromLocation(doublelatitude,doublelongitude,intmaxResults)

List<Address>getFromLocationName(StringlocationName,intmaxResults,double

lowerLeftLatitude,doublelowerLeftLongitude,doubleupperRightLatitude,doubleupperRightLongitude)

List<Address>getFromLocationName(StringlocationName,intmaxResults)

Itturnsoutthatcomputinganaddressisnotanexactsciencebecauseofthevariouswaysalocationcanbedescribed.Forexample,thegetFromLocationName()methodscantakethenameofaplace,thephysicaladdress,anairportcode,orsimplyawell-knownnameforthelocation.Thus,themethodsreturnalistofaddressesandnotasingleaddress.Becausethemethodsreturnalist,whichcouldbequitelong(andtakealongtimetoreturn),youareencouragedtolimittheresultsetbyprovidingavalueformaxResultsthatrangesbetween1and5.Now,let’sconsideranexample.

Listing19-6showstheXMLlayoutandcorrespondingcodefortheactivityandmapfragmentshowninFigure19-5.Toruntheexample,you’llneedtoupdatethemanifestwithyourownMapsAPIkey.

Listing19-6.WorkingwiththeAndroidGeocoderClass

<!--Thisisactivity_main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.androidbook.maps.whereami.MainActivity"tools:ignore="MergeRootFrame">

<EditTextandroid:id="@+id/locationName"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Enterlocationname"android:inputType="text"android:imeOptions="actionGo"/>

<FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"/>

</LinearLayout>

/***ThisisfromMainActivity.java**/publicclassMainActivityextendsFragmentActivity{

privatestaticfinalStringMAPFRAGTAG="MAPFRAGTAG";MyMapFragmentmyMapFrag=null;privateGeocodergeocoder;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();}if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD&&!Geocoder.isPresent()){Toast.makeText(this,"Geocoderisnotavailableonthisdevice",Toast.LENGTH_LONG).show();finish();}geocoder=newGeocoder(this);EditTextloc=(EditText)findViewById(R.id.locationName);loc.setOnEditorActionListener(newOnEditorActionListener(){@OverridepublicbooleanonEditorAction(TextViewv,intactionId,KeyEventevent){if(actionId==EditorInfo.IME_ACTION_GO){StringlocationName=v.getText().toString();

try{List<Address>addressList=

geocoder.getFromLocationName(locationName,5);

if(addressList!=null&&addressList.size()>0){//Log.v(TAG,"Address:"+addressList.get(0).toString());myMapFrag.gotoLocation(newLatLng(addressList.get(0).getLatitude(),addressList.get(0).getLongitude()),locationName);}}catch(IOExceptione){e.printStackTrace();}}returnfalse;}});}}

publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{privateGoogleMapmMap=null;

publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}

@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}

publicvoidgotoLocation(LatLnglatlng,StringlocString){if(mMap==null)return;//Addamarkerforthegivenlocation

MarkerOptionsmarkerOpt=newMarkerOptions().draggable(false).flat(false).position(latlng).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)).title("Youchose:").snippet(locString);//SeetheonMarkerClickedcallbackforwhywedothismMap.addMarker(markerOpt);

//MovethecameratozoominonourlocationmMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latlng,15));}

@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;}}

Figure19-5.Geocodingtoapointgiventhelocationname

TodemonstratetheusesofgeocodinginAndroid,typethenameoraddressofalocationintheEditTextfieldandthentaptheGobuttononthekeyboard.Tofindtheaddressofalocation,wecallthegetFromLocationName()methodofGeocoder.Thelocationcanbeanaddressorawell-knownnamesuchas“WhiteHouse.”Geocodingcanbeaprolongedoperation,sowerecommendthatyoulimittheresultstofive,astheAndroiddocumentationsuggests.

ThecalltogetFromLocationName()returnsalistofaddresses.Thesampleapplicationtakesthelistofaddressesandprocessesthefirstoneifanywerefound.Everyaddresshasalatitudeandlongitude,whichyouusetocreateaLatLng.YouthencallourgotoLocation()methodtonavigatetothepoint.Thisnewmethodinthemapfragmentcreatesanewmarker,addsittothemap,andmovesthecameratothemarkerwithazoomlevelof15.Thezoomlevelcanbesettoafloatbetween1and21,inclusive.Asyoumovefrom1toward21by1’s,thezoomlevelincreasesbyafactorof2.Wecouldhavepresentedadialogtodisplaymultiplefoundlocationsifwewantedto,butfornow,we’lljustdisplaythefirstlocationreturnedtous.

Inourexampleapplication,weonlyreadthelatitudeandlongitudeofourreturned

Address.Infact,therecanbeatonofdataaboutAddressesreturnedtous,includingthelocation’scommonname,street,city,state,postal/ZIPcode,country,andevenphonenumberandwebsiteURL.Youshouldunderstandafewpointswithrespecttogeocoding:

WhiletheGeocoderclassmayexist,theservicemaynotbeimplemented.IfthedeviceisGingerbreadorhigher,youshouldcheckwithGeocoder.isPresent()beforeattemptingtogeocodeinyourapplication.

Areturnedaddressisnotalwaysanexactaddress.Obviously,becausethereturnedlistofaddressesdependsontheaccuracyoftheinput,youneedtomakeeveryefforttoprovideanaccuratelocationnametotheGeocoder.

Wheneverpossible,setthemaxResultsparametertoavaluebetween1and5.

YoushouldseriouslyconsiderdoingthegeocodingoperationinadifferentthreadfromtheUIthread.Therearetworeasonsforthis.Thefirstisobvious:theoperationistime-consuming,andyoudon’twanttheUItohangwhileyoudothegeocoding,causingAndroidtokillyouractivity.Thesecondreasonisthat,withamobiledevice,youalwaysneedtoassumethatthenetworkconnectioncanbelostandthattheconnectionisweak.Therefore,youneedtohandleinput/output(I/O)exceptionsandtimeoutsappropriately.Onceyouhavecomputedtheaddresses,youcanposttheresultstotheUIthread.SeetheincludedsampleapplicationcalledWhereAmIGeocoder2forhowtodothis.

UnderstandingLocationServicesLocationServicesprovidetwomainfunctions:amechanismforyoutoobtainthedevice’sgeographicallocationandafacilityforyoutobenotified(viaanintent)whenthedeviceentersorexitsaspecifiedgeographicallocation.Thislatteroperationisknownasgeofencing.

Inthissection,youaregoingtolearnhowtofindthedevice’scurrentlocation.Tousetheservice,youmustfirstobtainareferencetoit.Listing19-7showsasimpleusageoftheFusedLocationProviderApiservice.ThesampleprojectforthisiscalledWhereAmILocationAPI.

Listing19-7.UsingtheLocationProviderAPI

publicclassMyMapFragmentextendsSupportMapFragmentimplementsGoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener,OnMapReadyCallback{

privateContextmContext=null;privateGoogleMapmMap=null;privateGoogleApiClientmClient=null;privateLatLngmLatLng=null;

publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}

@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);if(mClient==null){//firsttimein,setupthisfragmentsetRetainInstance(true);

mContext=getActivity().getApplication();mClient=newGoogleApiClient.Builder(mContext,this,this).addApi(LocationServices.API).build();mClient.connect();}}

@OverridepublicvoidonConnectionFailed(ConnectionResultarg0){Toast.makeText(mContext,"Connectionfailed",Toast.LENGTH_LONG).show();}

@OverridepublicvoidonConnected(Bundlearg0){//Figureoutwhereweare(lat,long)asbestaswecan//basedontheuser'sselectionsforLocationSettingsFusedLocationProviderApilocator=LocationServices.FusedLocationApi;

LocationmyLocation=locator.getLastLocation(mClient);//iftheservicesarenotavailable,couldgetanulllocationif(myLocation==null)return;doublelat=myLocation.getLatitude();doublelng=myLocation.getLongitude();mLatLng=newLatLng(lat,lng);doWhenEverythingIsReady();}

@OverridepublicvoidonConnectionSuspended(intarg0){Toast.makeText(mContext,"Connectionsuspended",Toast.LENGTH_LONG).show();}

@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;doWhenEverythingIsReady();}

privatevoiddoWhenEverythingIsReady(){if(mMap==null||mLatLng==null)return;//AddamarkerMarkerOptionsmarkerOpt=newMarkerOptions().draggable(false).flat(true).position(mLatLng).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));mMap.addMarker(markerOpt);

//MovethecameratozoominonourlocationmMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mLatLng,15));}}

Toacquirealocationservice,youfirstneedtocreateaGoogleAPIclientobject,whichmakesavailabletoyoutheservicesfromGooglePlayServices.Thisisrelativelyeasytodo,andonceyouhavetheclientobject,youneedtocallitsconnect()method.ThiswilllaterinvoketheonConnected()callbackasynchronouslytoletyourapplicationknowthattheclienthasbeenconnectedandisnowavailableforuse.OryourapplicationmaygettheonConnectionFailed()callback,inwhichcaseyoushouldtake

appropriateaction.ForthesamplewesimplyshowaToastmessagewhentheconnectionattempthasfailed.Lateronyou’llseehowtodealmorerobustlywithafailedconnection.

WhentheonConnected()callbackisinvoked,nowyoucanworkwiththelocationproviderAPI.Recallthatatthebeginningofthischapteryousetpermissionsinthemanifestfiletoaccesslocationinformation.FinelocationsuseGPSwhilecoarselocationsusecelltowersandWiFihotspots.UsingafusedlocationproviderAPImeansthatyourapplicationisn’tworryingaboutwhatisenabledorwhatpermissionsareset.TheAPIcallsarethesame.Youjustaskforlocationsandyouwillgetthebestlocationinformationthatisavailableatthetime.

Forthissample,wecallthegetLastLocation()method.Withluck,thelocationthatisreturnedisverycurrent;however,beawarethatthelastlocationmightbefromminutesorhoursago.TheLocationobjectcantellyou,viathegetTime()method,whenthislocationfixwasobtained.Youcouldchecktoseeifitisnewenoughforyourpurposesbeforedecidingtouseit.ItistechnicallypossiblethatgetLastLocation()willreturnnullsoyoushouldbepreparedforthatcaseaswell.ThiscanhappenifLocationServiceshavebeendisabledinSettings.

You’llseesoonhowtogetupdatestolocations.Fornow,thesampletakeswhateverthelastlocationwasandcreatesamapmarkeroutofitfordisplaytotheuser.Youshouldrecognizethecodetocreateamarkerfromtheprevioussectionofthischapter.

HowtoEnableLocationServicesYoumightthinkthere’sasimpleAPItoenableLocationServicesiftheyarenotturnedonwhenyourapplicationruns.Unfortunately,thisisnotthecase.TogetLocationServicesturnedon,theusermustdothatfromwithintheSettingsscreensoftheirdevice.YourapplicationcanmakethisalotsimplerfortheuserbylaunchingthatparticularSettingsscreen.Thelocationsettingssourcescreenisreallyjustanactivity,andthisactivityissetuptorespondtoanintent.

Inthesampleapplicationjustcovered,youwillseethecodefromListing19-8intheactivity’sonCreate()callback.

Listing19-8.CheckingtoSeeIfLocationServicesAreOn

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();

}

if(!isLocationEnabled(this)){//nolocationserviceprovidersareenabledToast.makeText(context,"LocationServicesappeartobeturnedoff."+"Thisappcan'tworkwithoutthem.Pleaseturnthemon.",Toast.LENGTH_LONG).show();startActivityForResult(newIntent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS),0);}}

@SuppressWarnings("deprecation")publicbooleanisLocationEnabled(Contextcontext){intlocationMode=Settings.Secure.LOCATION_MODE_OFF;StringlocationProviders;

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){try{locationMode=Settings.Secure.getInt(context.getContentResolver(),Settings.Secure.LOCATION_MODE);}catch(SettingNotFoundExceptione){e.printStackTrace();}returnlocationMode!=Settings.Secure.LOCATION_MODE_OFF;}else{locationProviders=Settings.Secure.getString(context.getContentResolver(),Settings.Secure.LOCATION_PROVIDERS_ALLOWED);return!TextUtils.isEmpty(locationProviders);}}

AchangeoccurredinAndroid19(KitKat)wherenewsettingsvalueswereaddedtothestaticSettings.Secureclass.ThismadeiteasiertotellifLocationServiceswereturnedonornot,andwhichones,buttheuserstillneedstodotheworktoenabletheservices.Therearetwowaysinthiscodetocheckforservices:useoneofthenewvalues,ordoagetontheavailablelocationproviders.ThefirstpartofListing19-8checkstoseeiftheversionofAndroidisKitKatorhigher,andifsoitlooksforthevalueofthenewSettingforlocationmode.Thesecondpartofthecode(iftheversionofAndroidisolderthanKitKat)doesagetontheallowedlocationproviders.Iflocationmodeisnotoff,orifthereisatleastonelocationprovideravailable,thenLocationServicesarerunning.Ifnot,

thiscodelaunchesanintenttotheLocationSettingsscreen.Atthatpoint,thisactivitywouldbepausedwhiletheSettingsactivityruns.WhentheSettingsactivityisdone,ouractivitywillresume.

IfyouwanttohandlearesponsefromtheSettingsactivity(i.e.,benotifiedwhenthatactivityisdoneandpresumablyasettingchangehasbeenmade),youmustimplementtheonActivityResult()callbackinyouractivity.Andalsokeepinmindthatalthoughyouhopetheuserturnsonlocationservices,theymaynot.Youwillneedtocheckagaintoseeiftheuserhasenabledlocationservicesandtakeappropriateactionbasedontheresult.We’llshowyouhowtodoallofthisinalatersection.

LocationProvidersYou’veseentheFusedLocationApi,butyoushouldalsobeawareoftheolder,alternatelocationproviders.Thehardwareisrightthereonthedeviceforgettinglocationinformation,andthelocationproviderswillgiveittoyourapplication.You’llsoonseehowtheFusedLocationApihandlesyourlocationneedsatahigherlevelthantheseproviders.Butifyouneedtodigintothedetails,forexampletocheckthestatusoftheavailableGPSsatellites,you’llbehappytoknowtheseprovidersexist.GooglerecommendsthateveryoneswitchovertotheFusedLocationApi;butsinceitreliesonGooglePlayServices,thatmeansapplicationsthatuseFusedLocationApiwillnotrunonanon-GoogleAndroiddevice.

TheLocationManagerserviceisasystem-levelservice.System-levelservicesareservicesthatyouobtainfromthecontextusingtheservicename;youdon’tinstantiatethemdirectly.Theandroid.app.ActivityclassprovidesautilitymethodcalledgetSystemService()thatyoucanusetoobtainasystem-levelservice.YoucallgetSystemService()andpassinthenameoftheserviceyouwant,inthiscase,Context.LOCATION_SERVICE.You’llseethisshortlyinListing19-9.

TheLocationManagerserviceprovidesgeographicallocationdetailsbyusinglocationproviders.Currently,therearethreetypesoflocationproviders:

GPSprovidersuseaGlobalPositioningSystemtoobtainlocationinformation.

Networkprovidersusecell-phonetowersorWiFinetworkstoobtainlocationinformation.

Thepassiveproviderislikealocationupdatesniffer,anditpassestoyourapplicationlocationupdatesthatarerequestedbyotherapplications,withoutyourapplicationhavingtospecificallyrequestanylocationupdates.Ofcourse,ifnooneelseisrequestinglocationupdates,youwon’tgetanyeither.

SimilartotheFusedLocationApi,theLocationManagerclasscanprovidethedevice’slastknownlocation,thistimeviathegetLastKnownLocation()method.Locationinformationisobtainedfromaprovider,sothemethodtakesasaparameterthenameoftheprovideryouwanttouse.Validvaluesforprovidernamesare

LocationManager.GPS_PROVIDER,LocationManager.NETWORK_PROVIDER,andLocationManager.PASSIVE_PROVIDER.Notethatthereisnooptionforafusedprovider,sincethatisaseparatelocation-findingcapability.

Inorderforyourapplicationtosuccessfullygetlocationinformation,itmusthavetheappropriatepermissionsintheAndroidManifest.xmlfile.android.permission.ACCESS_FINE_LOCATIONisrequiredforGPSandforpassiveproviders,whereasandroid.permission.ACCESS_COARSE_LOCATIONorandroid.permission.ACCESS_FINE_LOCATIONcanbeusedfornetworkproviders,dependingonwhatyouneed.Forinstance,assumeyourapplicationwilluseGPSornetworkdataforlocationupdates.BecauseyouneedACCESS_FINE_LOCATIONforGPS,you’vealsosatisfiedpermissionsfornetworkaccess,soyoudonotneedtoalsospecifyACCESS_COARSE_LOCATION.Ifyou’reonlygoingtousethenetworkprovider,youcouldgetbywithonlyACCESS_COARSE_LOCATIONinthemanifestfile.

CallinggetLastKnownLocation()returnsanandroid.location.Locationinstance,ornullifnolocationisavailable.TheLocationclassprovidesthelocation’slatitudeandlongitude,thetimethelocationwascomputed,andpossiblythedevice’saltitude,speed,andbearing.ALocationobjectcanalsotellyouwhichprovideritcamefromusinggetProvider(),whichwillbeeitherGPS_PROVIDERorNETWORK_PROVIDER.Ifyou’regettinglocationupdatesviathePASSIVE_PROVIDER,rememberthatyou’reonlyreallysniffinglocationupdates,soallupdatesareultimatelyfromeitherGPSorthenetwork.

BecausetheLocationManageroperatesonproviders,theclassprovidesAPIstoobtainproviders.Forexample,youcangetalloftheknownprovidersbycallinggetAllProviders().YoucanobtainaspecificproviderbycallinggetProvider(),passingthenameoftheproviderasanargument(suchasLocationManager.GPS_PROVIDER).OnethingtowatchoutforisthatgetAllProviders()willreturnprovidersthatyoumaynothaveaccesstoorthatarecurrentlydisabled.Fortunately,youareabletodeterminethestatusofprovidersusingothermethods,suchasisProviderEnabled(StringproviderName)orgetProviders(booleanenabledOnly),whichyoucouldcallwithavalueoftruetogetonlyprovidersyouareabletouseimmediately.

There’sanotherwaytogetasuitableprovider,andthatistousethegetProviders(Criteriacriteria,booleanenabledOnly)methodofLocationManager.Byspecifyingcriteriaforlocationupdates,andbysettingenabledOnlytotruesoyougetprovidersthatareenabledandreadytogo,youcangetalistofprovidernamesreturnedtoyouwithouthavingtoknowthespecificsofwhichprovideryougot.Thiscouldbemoreportable,becauseadevicemayhaveacustomLocationProviderthatmeetsyourneedswithoutyouhavingtoknowaboutitinadvance.TheCriteriaobjectcanbesetwithparametersthatincludeaccuracylevelandtheneedforinformationaboutspeed,bearing,altitude,cost,andpowerrequirements.

Ifnoprovidersmeetyourcriteria,anulllistwillbereturned,allowingyoutoeitherbailoutorrelaxthecriteriaandtryagain.

SendingLocationUpdatestoYourApplicationWhendoingdevelopmenttesting,yourapplicationneedslocationinformation,andtheemulatordoesn’thaveaccesstoGPSorcelltowers.Inorderforyoutotestyourapplicationintheemulator,youcanmanuallysendlocationupdatesfromEclipse.Listing19-9showsasimpleexampletoillustratehowtodothis.We’regoingtostickwiththeLocationManagerapproachhere,andthenshowtheFusedLocationApiapproachlater.

Listing19-9.RegisteringforLocationUpdates

publicclassLocationUpdateDemoActivityextendsActivity{LocationManagerlocMgr=null;LocationListenerlocListener=null;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);

locMgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE);

locListener=newLocationListener(){publicvoidonLocationChanged(Locationlocation){if(location!=null){Toast.makeText(getBaseContext(),"Newlocationlatitude["+location.getLatitude()+"]longitude["+location.getLongitude()+"]",Toast.LENGTH_SHORT).show();}}

publicvoidonProviderDisabled(Stringprovider){}

publicvoidonProviderEnabled(Stringprovider){}

publicvoidonStatusChanged(Stringprovider,intstatus,Bundleextras){}};}

@OverridepublicvoidonResume(){super.onResume();

locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,//minTimeinms0,//minDistanceinmeterslocListener);}

@OverridepublicvoidonPause(){super.onPause();locMgr.removeUpdates(locListener);}}

We’renotdisplayingauserinterfaceforthisexample,sothestandardinitiallayoutXMLfilewilldo,aswellasaregularactivity.

OneoftheprimaryusesoftheLocationManagerserviceistoreceivenotificationsofthedevice’slocation.Listing19-9demonstrateshowyoucanregisteralistenertoreceivelocation-updateevents.Toregisteralistener,youcalltherequestLocationUpdates()method,passingtheprovidertypeasoneoftheparameters.Whenthelocationchanges,theLocationManagercallstheonLocationChanged()methodofthelistenerwiththenewLocation.Itisveryimportantthatyouremoveanyregistrationsforlocationupdatesattheappropriatetime.Inourexample,wedoregistrationinonResume(),andweremovethatregistrationinonPause().Ifwearen’tgoingtobearoundtodoanythingwithlocationupdates,weshouldtelltheprovidernottosendthem.There’salsothepossibilitythatouractivitycouldbedestroyed(forexample,iftheuserrotatestheirdeviceandouractivityisrestarted),inwhichcaseouroldactivitycouldstillexist,bereceivingupdates,displayingthemwithToast,andtakingupmemory.

Inourexample,wesettheminTimeandminDistancetozero.ThistellstheLocationManagertosendusupdatesasoftenaspossible.Thesearenotdesiredsettingsforyourproductionapplication,oronrealdevices,butweusethemheretomakethedemonstrationsrunbetterintheemulators.(Inreallife,youwouldnotwantthehardwaretryingtofigureoutourcurrentpositionsooften,asthisdrainsthebattery.)Setthesevaluesappropriatelyforthesituation,tryingtominimizehowoftenyoutrulyneedto

benotifiedofachangeinposition.Googletypicallyrecommendsvaluesnosmallerthan20seconds.

TestingLocationApplicationswiththeEmulatorLet’stestthisintheemulator,usingtheDalvikDebugMonitorService(DDMS)perspectivethatshipswiththeADTplug-inforEclipse.TheDDMSUIprovidesascreenforyoutosendtheemulatoranewlocation(seeFigure19-6).

Figure19-6.UsingtheDDMSUIinEclipsetosendlocationdatatotheemulator

TogettotheDDMSinEclipse,useWindow OpenPerspective DDMS.TheEmulatorControlviewshouldalreadybethereforyou,butifnot,useWindow ShowViewOther Android EmulatorControltomakeitvisibleinthisperspective.Youmayneedtoscrolldownintheemulatorcontroltofindthelocationcontrols.AsshowninFigure19-6,theManualtabintheDDMSuserinterfaceallowsyoutosendanewGPSlocation(latitude/longitudepair)totheemulator.SendinganewlocationwillfiretheonLocationChanged()methodonthelistener,whichwillresultinamessagetotheuserconveyingthenewlocation.

Youcansendlocationdatatotheemulatorusingseveralothertechniques,asshownintheDDMSuserinterface(seeFigure19-6).Forexample,theDDMSinterfaceallowsyoutosubmitaGPSExchangeFormat(GPX)fileoraKeyholeMarkupLanguage(KML)file.

YoucanobtainsampleGPXfilesfromthesesites:

www.topografix.com/gpx_resources.asp

http://tramper.co.nz/?view=gpxFiles

www.gpsxchange.com/

Similarly,youcanusethefollowingKMLresourcestoobtainorcreateKMLfiles:

http://bbs.keyhole.com/

http://code.google.com/apis/kml/documentation/kml_tut.html

NoteSomesitesprovideKMZfiles.ThesearezippedKMLfiles,sosimplyunzipthemtogettotheKMLfile.SomeKMLfilesneedtohavetheirXMLnamespacevaluesalteredinordertoplayproperlyinDDMS.IfyouhavetroublewithaparticularKMLfile,makesureithasthis:

<kmlxmlns=”http://earth.google.com/kml/2.x“>.

YoucanuploadaGPXorKMLfiletotheemulatorandsetthespeedatwhichtheemulatorwillplaybackthefile(seeFigure19-7).Theemulatorwillthensendlocationupdatestoyourapplicationbasedontheconfiguredspeed.AsFigure19-7shows,aGPXfilecontainspoints,showninthetoppart,andpaths,showninthebottompart.Youcan’tplayapoint,butwhenyouclickapoint,itwillbesenttotheemulator.Youclickapath,andthenthePlaybuttonwillbeenabledsoyoucanplaythepoints.

Figure19-7.UploadingGPXandKMLfilestotheemulatorforplayback

NoteTherehavebeenreportsthatnotallGPXfilesareunderstandablebytheemulatorcontrol.IfyouattempttoloadaGPXfileandnothinghappens,tryadifferentfilefromadifferentsource.

Listing19-9includessomeadditionalmethodsforLocationListenerwehaven’tmentionedyet.TheyarethecallbacksonProviderDisabled(),onProviderEnabled(),andonStatusChanged().Foroursample,wedidnotdoanythingwiththese,butinyourapplication,youcouldbenotifiedwhenalocationprovider,suchasgps,isdisabledorenabledbytheuser,orwhenastatuschangeswithoneofthelocationproviders.StatusesincludeOUT_OF_SERVICE,TEMPORARILY_UNAVAILABLE,andAVAILABLE.Evenifaproviderisenabled,itdoesnotmeanthatitwillbesendinganylocationupdates,andyoucantellthatusingstatuses.NotethatonProviderDisabled()willbeinvokedimmediatelyifarequestLocationUpdates()iscalledforadisabledprovider.

SendingLocationUpdatesfromtheEmulatorConsoleEclipsehassomeeasy-touse-toolsforsendinglocationupdatestoyourapplication,butthere’sanotherwaytodoit.Youcouldlaunchtheemulatorconsole,usingthefollowing

commandfromatoolswindow:

telnetlocalhostemulator_port_number

whereemulator_port_numberisthenumberassociatedtotheinstanceoftheAVDthat’salreadyrunning,displayedinthetitlebaroftheemulatorwindow.Youmayneedtoinstalltelnetforyourworkstationifit’snotalreadyavailable.Onceyou’reconnected,youcanusethegeofixcommandtosendinlocationupdates.Tosendinlatitude/longitudecoordinateswithaltitude(altitudeisoptional),usethisformofthecommand:

geofixlonlat[altitude]

Forexample,thefollowingcommandwillsendthelocationofJacksonville,Floridatoyourapplicationwithanaltitudeof120meters.

geofix-81.562530.334954120

Pleasepaycarefulattentiontotheorderoftheargumentstothegeofixcommand.Longitudeisthefirstargument,andlatitudeisthesecond.

WhatCanYouDowithaLocation?Asmentionedbefore,Locationscantellyouthelatitudeandlongitude,whentheLocationwascomputed,theproviderthatcomputedthisLocation,andoptionallythealtitude,speed,bearing,andaccuracylevel.DependingontheproviderwheretheLocationcamefrom,therecouldbeextrainformationaswell.Forexample,iftheLocationcamefromaGPSprovider,thereisanextrasBundlethatwilltellyouhowmanysatelliteswereusedtocomputetheLocation.Theoptionalvaluesmayormaynotbepresent,dependingontheprovider.ToknowifaLocationhasoneofthesevalues,theLocationclassprovidesasetofhas…()methodsthatreturnabooleanvalue,forexamplehasAccuracy().BeforerelyingonthereturnvalueofgetAccuracy(),itwouldbewisetocallhasAccuracy()first.

TheLocationclasshassomeotherusefulmethods,includingastaticmethoddistanceBetween(),whichwillreturntheshortestdistancebetweentwoLocations.Anotherdistance-relatedmethodisdistanceTo(),whichwillreturntheshortestdistancebetweenthecurrentLocationobjectandtheLocationobjectpassedtothemethod.NotethatdistancesareinmetersandthatthedistancecalculationstakeintoaccountthecurvatureoftheEarth.Butalsobeawarethatthedistancesarenotprovidedintermsofthedistanceyouwouldhavetogobycar,forexample.

Ifyouwanttogetdrivingdirectionsordrivingdistances,youwillneedtohaveyourbeginningandendingLocations,buttodothecalculations,youwilllikelyneedtousetheGoogleDirectionsAPI.TheDirectionsAPIwouldallowyourapplicationtoshowhowtogetfromyourbeginningtoyourendinglocation.ThisisanotheroftheGoogleAPIclientAPIsthatyoucanenableforyourapplication.

SettingUpforGooglePlayServicesLocationUpdates

You’veseenhowtogetlocationupdateswithaLocationManager,butlet’sreturntotheFusedLocationProviderApitoseehowtogetlocationupdatesfromit.ThesampleprojectforthissectionisFusedLocationApiUpdates.ThisoneisabittrickierbecausewearedealingwithGooglePlayServices,anindependentservicerunningonthedevice.Therefore,youcan’talwaysbesurethatyouhaveavalidclientconnection,andyouneedtobecarefulwhenrequestinglocationupdates.Forthisreason,yourapplicationwillneedtoworryaboutstate.

Intheearliersampleprogram(WhereAmILocationAPI),youcheckedtoseeifLocationServiceswereturnedon,butthecodeassumedthatGooglePlayServiceswereavailableandready.Nowyou’regoingtoseehowtocheckfortheexistenceofGooglePlayServicesandhowtheGooglePlayServicesUtilclasscanhelpyou.Thebasicflowistocheckeachdependencyforlocationupdatestooccurand,ifthereisawaytocorrectaproblem,helptheuserfixit.Iftheuserdoesnot,orcannot,fixaproblem,theapplicationquits.Iftheuserkeepsfixingproblemsuntileverythingisworking,thenlocationupdatesgetrequested,andtheapplicationdisplayslocationupdatesviaToastmessages.

Listing19-10showsourmainmethodfortryingtoconnect.YouwillseeinsidethismethodthesameLocationServicescheckfromtheearlierWhereAmILocationAPIsampleapplication.ThetryToConnect()methodwillbecalledfromtheactivity’sonResume()callback,sothateverytimethisactivityisresumed,anewclientconnectionwillbeestablishedtoGooglePlayServices.Wedonotwanttoassumethatanoldclientisstillvalidandactive.

Listing19-10.CheckingfortheAbilitytoDoLocationUpdates

privatevoidtryToConnect(){//CheckthatGooglePlayservicesisavailableintresultCode=GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);//IfGooglePlayservicesisavailable,thenwe'regoodif(resultCode==ConnectionResult.SUCCESS){Log.d(TAG,"GooglePlayservicesisavailable.");if(!isLocationEnabled(this)){if(lastFix==FIX.LOCATION_SETTINGS){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Locationsettingsdidn'twork");finish();}else{//nolocationserviceprovidersareenabledToast.makeText(this,"LocationServicesareoff."+"Can'tworkwithoutthem.Pleaseturnthemon.",Toast.LENGTH_LONG).show();Log.i(TAG,"LocationServicesneedtobeon."

+"LaunchingtheSettingsscreen");startActivityForResult(newIntent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS),LOCATION_SETTINGS_REQUEST);lastFix=FIX.LOCATION_SETTINGS;}}else{client.connect();Log.v(TAG,"ConnectingtoGoogleApiClient…");}}//GooglePlayserviceswasnotavailableforsomereason//Seeiftheusercandosomethingaboutitelseif(GooglePlayServicesUtil.isUserRecoverableError(resultCode)){if(lastFix==FIX.PLAY_SERVICES){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Recoverydoesn'tseemtowork");finish();}else{Log.d(TAG,"GooglePlayservicesmaybeavailable."+"Askinguserforhelp");//Thisformofthedialogcallwillresultineithera//callbacktoonActivityResult,oradialogonCancel.GooglePlayServicesUtil.showErrorDialogFragment(resultCode,this,PLAY_SERVICES_RECOVERY_REQUEST,this);lastFix=FIX.PLAY_SERVICES;}}else{//Nohopeleft.Log.e(TAG,"GooglePlayServicesis/arenotavailable."+"Nopointincontinuing");finish();}}

TheGooglePlayServicesUtilclasshasseveralstaticmethodstohelpgeteverythingsetup

forlocationupdates.ThefirstmethodisisGooglePlayServicesAvailable(),whichrequiresacontext.TheresultisanintegervaluewhichiseitherSUCCESSoroneofseveralothervalueswhichcouldindicateforexamplethattheservicesaremissing,ortheversionisnotappropriate.Formostpurposes,youdon’treallyneedtocareabouttheothervaluesthatarereturned,asyou’llsee.

IfGooglePlayServicesareavailable,youwillcheckforLocationServices(asbefore)andiftheyareokay,youcaninvoketheconnect()methodontheGoogleApiClientclient.Theconnect()callisasynchronousandaseparatecallbackwillhandletheresultsoftheconnectcall.Asbefore,ifLocationServicesarenotturnedon,youwouldlaunchthelocationsettingsactivitysotheusercouldturnthemon.Inthissample,wejustuseaToastmessagetotelltheuserwhytheyarebeingredirectedtotheSettingsscreen.Inaproductionapplication,youwouldprobablywanttoshowanalertdialogwithanOKandCancelbuttonbeforeredirectingtotheSettingsscreen.

IfGooglePlayServicesarenotavailable,thenextcheckistoseeiftheusercouldresolvetheissue,usingtheisUserRecoverableError()method.Hereyoupassintheresultcodefromtheearliercheck,whichshouldbesomethingotherthanSUCCESS.Thisiswhyyoudon’tneedtocarewhatothervaluewasreturned.Thismethoddecidesforyouiftheusercandosomethingaboutitornot.Iftheusercan’tcorrectthesituation(i.e.,isUserRecoverableError()returnsfalse),thentherereallyisn’tanythingelseyoucandoandyouwillprobablywanttobailout.Inthissampleapplicationalogmessageiswrittenandtheactivityends.Youmightwanttobemoregracefulinyourexit.

IftheusercandosomethingabouttheproblemwithGooglePlayServices,theGooglePlayServicesUtilclasshasyetanotherstaticmethodyoucanuse:showErrorDialogFragment().Thiswillshowadialogtotheuserindicatingwhattheproblemisandwhattheycandoaboutit.Thereareafewvariationsonthiscall,andthesampleisusingtheonewhichpopsadialogfragmentwhilelisteningforadialogcancel.Thedialogfragmentcouldlaunchanotheractivity,whichwouldresultinouronActivityResult()beingcalled.Forthisreason,youwanttopassinarequestvalue(i.e.,PLAY_SERVICES_RECOVERY_REQUEST),whichwillbepassedtoonActivityResult()later.Thismethodisalsoasynchronous,andyourapplicationwillseeeitheronActivityResult()invokedlater,ortheonCancel()forthedialog.ThesecondargumenttoshowErrorDialogFragment()isthecontext,andthelastargumentisthelistenerforthedialog.Becausewepassed'this'asthelastargument,torepresentthisactivity,thesampleactivitymustimplementDialogInterface.OnCancelListenerandhaveanonCancel()callback.

You’llsoonseethecodeforonActivityResult(),butyoushouldknowthatwhenaresultispassedbacktoyouractivity,you’regoingtohavedothesechecksagain,bycallingtryToConnect().ThatiswhythismethodsetsalastFixvalue,tokeeptrackofwhichproblemisbeingworkedon.Ifthesameproblemexistsaftertheuserhashadachancetofixit,wecouldassumethattheuserisn’tinterestedinfixingtheproblem,orthesystemisunabletofixtheproblem.Wedonotwantsomesortofinfiniteloopthattheusercannotbreakoutof.Forthissampleactivity,iftryToConnect()hitsthesameproblemtwiceinarow,itbailsoutandtheactivityisfinished.Yourapplicationmight

wanttotakealternativeaction,givingtheusermoreoptionstocontinuetousetheapp.

TorecapwhathashappenedintryToConnect(),youcheckedfortheexistenceandreadinessofGooglePlayServices,aswellasLocationServices.Ifeverythinglookedgood,aconnectcallwasmadeontheGoogleClientApiclient.Iftheuserwasabletocorrectanything,asuitableintentwasfiredtolaunchanactivitytotakecareofit.Andifthesituationwashopeless,theactivityended.Nowlet’slookatthevariouscallbacksthatcouldresultfromtheseactions.

Iftheconnectionrequestwassuccessful,theonConnected()callbackwillfire.Listing19-11showswhatthislookslike.

Listing19-11.ClientIsConnectedSoRequestLocationUpdates

@OverridepublicvoidonConnected(Bundlearg0){//SetuplocationupdatesLog.v(TAG,"Connected!");lastFix=FIX.NO_FAIL;locator.requestLocationUpdates(client,locReq,this);Log.v(TAG,"Requestinglocationupdates(onConnected)...");}

Thisoneisprettystraightforward.IfwegotagoodconnectiontoGooglePlayServices,startaskingtheFusedLocationProviderApi(locator)forlocationupdates.You’llseemoreaboutlocReqlater,butfornowjustknowthatitisaLocationRequestobjectwithparametersthatdefinewhatkindsoflocationupdatesyourapplicationwants.Thismethodalsoresetsastatevariable(lastFix)whichwillmakemoresensesoon.

Iftheconnectionrequestwasnotsuccessful,theonConnectionFailed()callbackwillfire.Listing19-12showsthiscallback.

Listing19-12.HandlingaFailedConnectionAttempt

@OverridepublicvoidonConnectionFailed(ConnectionResultconnectionResult){/**GooglePlayservicescanresolvesomeerrorsitdetects.*Iftheerrorhasaresolution,trysendinganIntentto*startaGooglePlayservicesactivitythatcanresolve*theerror.*/if(connectionResult.hasResolution()){Log.i(TAG,"Connectionfailed,tryingtoresolveit…");

if(lastFix==FIX.CONNECTION){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Connectionretrydidn'twork");finish();}try{//StartanactivitythattriestoresolvetheerrorlastFix=FIX.CONNECTION;connectionResult.startResolutionForResult(this,CONNECTION_FAILURE_RESOLUTION_REQUEST);}catch(IntentSender.SendIntentExceptione){//LogtheerrorLog.e(TAG,"Couldnotresolveconnectionfailure");e.printStackTrace();finish();}}else{/**Ifnoresolutionisavailable,displayerrortothe*user.*/Log.e(TAG,"Connectionfailed,noresolutionsavailable,"+GooglePlayServicesUtil.getErrorString(connectionResult.getErrorCode()));Toast.makeText(this,"Connectionfailed.Cannotcontinue",Toast.LENGTH_LONG).show();finish();}}

Iftheconnectionrequesthasfailed,itisstillpossiblethatthesituationcanbecorrected.Onceagainthereisamethodthatcantellifthereisawaytoresolvetheproblem.TheConnectionResultobjectcontainsbothanindicatorifthereisaresolution,aswellastheintenttofiretotrytoresolvethesituation.Inthiscase,theapplicationcallsstartResolutionForResult().Similartobefore,anintentwillbefired,someactivitywillbelaunched,andyourapplicationwillgetaresultbackinonActivityResult().NoticethatheretherequesttagisCONNECTION_FAILURE_RESOLUTION_REQUEST.Ifnothingcanbedone,displayanerrorandbailout.

Therecouldhavebeenseveralintentslaunched,eachofwhichshouldcauseyour

onActivityResult()callbacktofire.Listing19-13showswhatthiscallbacklookslike.Rememberthattherecouldhavebeenthreeseparateintentsfiredtohandleproblems,sothiscallbackmustexpectanyofthethree.Alsokeepinmindthattheintentscausedanactivitytorun,meaningyouractivitygotpaused,anditwillresumerightaftertheonActivityResult()hasfired.ThisisamajorreasonwhythetryToConnect()method(showninListing19-10)isonlycalledfromtheactivity’sonResume()callback.Wheneverthisactivityisbeingresumed,ittriestomakeanewconnectiontoGooglePlayServicesandtosetuplocationupdates.Whenthisactivitypauses,itdisconnectsfromGooglePlayServices.Itiseasytoreconnectratherthantryingtohangontoaconnectionwhileitisnotneeded.

Listing19-13.GettingNewsBackfromtheLaunchedIntents

@OverrideprotectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){/*Decidewhattodobasedontheoriginalrequestcode.*Notethatouractivitygotpausedtolaunchtheother*activity,soafterthiscallbackruns,ouractivity's*onResume()willrun.*/switch(requestCode){casePLAY_SERVICES_RECOVERY_REQUEST:Log.v(TAG,"GotaresultforPlayServicesRecovery");break;caseLOCATION_SETTINGS_REQUEST:Log.v(TAG,"GotaresultforLocationSettings");break;caseCONNECTION_FAILURE_RESOLUTION_REQUEST:Log.v(TAG,"Gotaresultforconnectionfailure");break;}Log.v(TAG,"resultCodewas"+resultCode);Log.v(TAG,"EndofonActivityResult");}

SinceonActivityResult()couldbecalledbecauseofanumberofintents,theswitchstatementisusedtofigureoutwhichoneisbeingrespondedto.TheGooglePlayServicescorrectiveactionmightsayitwassuccessfulbysettingtheresultCodetoActivity.RESULT_OK.Thisdoesn’tnecessarilymeanthattheuserfixedtheproblem,butittellsyouthatnothingfailed.IftheresponsetotheGooglePlayServicescorrectiveactionisActivity.RESULT_CANCELED,itcouldmeantherewassomesortoffailure.Regardlessiftheuserfixedtheproblemornot,you’regoingtoreturnfromthiscallback,andthenonResume()willrun,inwhichtryToConnect()willbecalledagain.Soitreallydoesn’tmatterwhatresultCodeis.Inpractice,evenwhenasettinghasbeenproperlysetforlocationupdatestooccur,youcouldseeresultCodesetto

RESULT_CANCELED.Similarly,ifthere’saresponsetotheotherfixes,logitandcontinuesinceonResume()willrunnextanyway.

Finally,referbacktotheonConnected()callbackinListing19-11,whichcallslocator.requestLocationUpdates(client,locReq,this).ThisiswheretheFusedLocationProviderApiwillbeaskedtosendlocationupdatesbacktothisactivity.GooglePlayServicesisupandrunning,andLocationServicesaresetappropriately.

Oncelocationupdateshavebeenrequested,anynewlocationupdateswillgetsenttotheonLocationChanged()callback.Inthissampleapplication,allthathappensisthatthelocationinformationisdisplayedinaToastmessage.Thenextsectiongoesintomoredetailonhowtorequestlocationupdates.

Thereareafewothermethodsintheactivitythatsofarwerenotdescribed.TheonPause()callbackdisconnectstheclientafterstoppingthelocationupdates.Youshouldnoticethattheclientischeckedforconnectednessbeforecallingmethods.TheGoogleApiClientclasshasamethodcalledisConnected(),whichyouwillusetobesureyourequestorremovelocationupdatesonlywhenthere’saconnectedclient.Otherwise,youwillgetanIllegalStateException.Thetwomethodsforsettingupthemenuarebasicmenucallbacks.Themenuisusedtoallowtheusertoswitchbetweenthevariouspriorityvalues.Whentheuserselectsamenuitem,thelocationrequestobjectisupdatedandpassedbackintoalterthelocationupdateprocess.TheonCancel()callbackcanbecalledfromthepop-uperrordialogthatisshownintryToConnect(seeListing19-10).Iftheusersimplyclosestheerrorfragmentdialogbox,weinferthattheuserdoesn’twanttogetupdatesandtheapplicationexits.

LocationUpdateswithFusedLocationProviderApiWiththeLocationManager,youhadtodealwiththespecificlocationproviders(i.e.,GPSorcell/WiFi).WiththeFusedLocationProviderApi,yousubmitaLocationRequestandtheAPIwillmakechoicesforyouofwhichproviderwouldbethebest,notonlyinitiallybutovertimeaswell.Ingeneral,thetrade-offwhengettinglocationupdatesisbetweenpowerconsumptionandaccuracy.GPSisusuallymoreaccuratebutusesthemostpower.Ontheotherhand,whenindoors,GPSmaybelessaccuratethancell/WiFi,andyou’dwanttoautomaticallyswitchtobemoreaccuratewhileconsumingtheleastamountofpower.TheFusedLocationProviderApicouldalsotakeadvantageofon-boardsensorssuchasagyroscopeorcompass.ThisAPIhidesthecomplexitiesoflocationfixingfromyou.

Youshouldwriteyourcodesoyou’rerequestinglocationupdatesonlywhenitmakessensetodoso.Ifyouaredisplayingthecurrentlocationonamap,andthemapisnotvisible,youdonotneedtorequestupdates.Therearecaseswhenyoumightwanttokeepgettingupdatesevenwhennotdisplayingthecurrentposition,andwe’llcoverthatinthenextsection.Thepointisthatlocationupdatescanbeabigdrainonthebattery,soaskforthemonlywhenyoureallyneedthem.Youshouldnotassumethattheuserisgoingto“berightback”andthereforekeepgettingupdates.Iftheysettheirdevicedownandwon’tbe

lookingatitagainforsometime,you’dbetternotbedrainingthebatterydown.

Listing19-14showshowthesampleapplicationsetstheLocationRequestobjecttomakealocationupdatesrequestoftheFusedLocationProviderApi.ThisisdoneintheonCreate()callbackoftheactivity.

Listing19-14.SettingUpaLocationRequestObject

locReq=LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000);

Usethestaticcreate()method,thencalltheappropriatesetterstofillouttherequestobject.ThisobjectwillbepassedtotherequestLocationUpdates()methodoftheFusedLocationProviderApi.Abigdifferencefromdealingwiththeolderlocationprovidersisthatthisrequestobjectdoesnotmakeanyreferencetoaspecificlocationprovider.SimilartotheCriteriamethodoffindingaprovider,thisrequestobjectultimatelyselectsthefrequencyofupdatesandtheconsumptionofpower.

YoucanspecifythedesiredfrequencyoflocationupdatesusingsetInterval()andsetFastestInterval();bothtakealongargumentrepresentingthenumberofmilliseconds.Theformerissayingthatyouwanttogetalocationupdateonaregularbasis,everysomanymillisecondsapart.Thesystemwilltrytohonorthisifitisableto,buttherearenoguarantees.Youcouldgetupdatesmorefrequentlythandesired,evenmuchmorefrequently.Thatiswherethesecondmethodcomesin.Youcanspecifythefastestintervalforreceivinglocationupdates.Moreonthisinabit.

ThepowerportionoftherequestishandledbythesetPriority()setter.Therearecurrentlyfouroptionsfortheargument:

PRIORITY_NO_POWER

PRIORITY_LOW_POWER

PRIORITY_BALANCED_POWER_ACCURACY

PRIORITY_HIGH_ACCURACY

TheNO_POWERoptionisprettymuchsayingthatyourapplicationwillbeusingthepassiveproviderdescribedearlier.Theonlywaytonotconsumeanypoweristopiggybackoffofthelocationupdatesforanotherapplication.Therefore,theaccuracyofthelocationsmaynotbeveryaccurateorfrequent;italldependsonwhatotherapplicationsarerequesting.YoujustlearnedthatyoucanrequestafrequencyofupdatesusingsetInterval()andsetFastestInterval().Ifyouarepiggybackingoffofanotherapplication,andthatapplicationisreceivinglocationupdatesevery5seconds,butyoudon’twantupdatesfasterthanevery20seconds,youshouldusesetFastestInterval(20000)soyourapplicationisnotoverwhelmedwith

updates.AtthesametimeyoucouldusesetInterval(60000)torequestadesiredintervalofoneupdateeveryminute.Iftherearefewotherlocationupdateshappeningonthedevice,youwon’thavetoworryaboutreducingthefrequencyfrom5secondsto20secondsapart,butatthesametimeyouprobablywon’tgetupdateseveryminuteeither.Youneedtousebothofthesesetterstoindicatewhatyourapplicationwants,butthatdoesn’tmeanyouareguaranteedtogetwhatyouwant.

TheLOW_POWERpriorityingeneralmeansthatlocationupdateswillbederivedonlyviacelltowertriangulationandWiFihotspotlocationinformation.Thesearelow-powerwaysofdeterminingposition,withacorrespondingreductioninaccuracy.Youcouldeasilyfindthelocationstobeaccurateonlytowithin1,500metersorworse,butthenyoucouldgetalocationthat’saccurateto10meters.Alloftheprioritieswilltakeadvantageofthepassiveprovider,soifanaccuratelocationupdatehappenstoberequestedbysomeotherapplication,yourapplicationcouldpickitupevenwhenyourpriorityissettolowpower.

TheBALANCEDprioritywilltrytodoadecentjoboftradingoffaccuracyforlesspower.Itwillconsiderusingalloftheavailablemethodsofdetermininglocation,exceptforGPS.

TheHIGH_ACCURACYprioritywillpotentiallyuseallavailablesourcesoflocationinformation,includingGPS.BecauseoftheGPSradio,thisprioritycouldconsumealotofbattery.

Locationupdatesalsodependonthelocationmodeofthedevice.Asyousawearlier,theLocationSettingschangedinKitKattoallowtheusertospecifyamodeoflocationupdatesfortheirdevice.ReferringnowtotheSettings.Secureclass,thelocationmodesettingvaluesareasfollows:

LOCATION_MODE_OFF

LOCATION_MODE_BATTERY_SAVING

LOCATION_MODE_HIGH_ACCURACY

LOCATION_MODE_SENSORS_ONLY

andthecurrentvaluecanberetrievedusingthecodefromListing19-8.Themodeissetbytheuserfortheentiredevice,notbyapplication.However,yourapplicationhasanopportunitytorequestaprioritytocomplementthemodechoicemadebytheuser.IfthedevicehasamodeofHIGH_ACCURACYandyourapplicationchoosesapriorityofLOW_POWER,yourapplicationwillnotbetheonedrainingthebatterybutcouldstillgetdecentlocationupdates.

Themodecanworkagainstyouhowever.IftheuserchoosesamodeofSENSORS_ONLY,andthepriorityissettoNO_POWER,LOW_POWERorevenBALANCED,locationupdateswillberare,regardlessofwhatyousetinthelocationrequestwithsetInterval().ThepreferredmodeformostusefullocationupdatesisHIGH_ACCURACY,becausethismodewillcombineallpossiblesourcesoflocationinformationandprovidethemostaccurateresults.Yourapplicationwillbeabletogethighaccuracywhenneeded(hopefullythisisarareneed)andgoodaccuracytherestof

thetime.YourapplicationcanaltertheprioritytoHIGH_ACCURACYwhenneeded,butBALANCEDorLOW_POWERtheothertimes.

SomeotherinterestingoptionswithaLocationRequestincludesettingaspecificnumberoflocationupdatestoreceive,ortospecifyatimelimitwhenthelocationupdatesshouldstop.Youcanalsosetaminimumdistance(inmeters)withinwhichyourapplicationdoesnotwantupdates.Thisisageofenceofsorts,whereyoutellthelocationservicethatyouonlywantalocationupdateifthedevicemovesacertaindistancefromitscurrentlocation.Thatisineffectsettingupageofencecirclearoundthecurrentlocation.Moreongeofenceslater.

AlternateWaysofGettingLocationUpdatesYou’veseenhowtogetlocationupdatessenttoyouractivityusingtherequestLocationUpdates()methodoftheLocationManagerandtheFusedLocationProviderApi.Thereareactuallyseveraldifferentsignaturesofthismethod,includingonesthatuseaPendingIntent.Thisgivesyoutheabilitytodirectlocationupdatestoservicesorbroadcastreceivers.YoucanalsodirectlocationupdatestootherLooperthreadsinsteadofthemainthread,givingyoulotsofflexibilityforyourapplication,althoughsomeofthesemethodshavebeenavailableonlysinceAndroid2.3.

UsingProximityAlertsandGeofencingGeofencingisapopularrequirementforamobileapplication.Itmeansthatyourapplicationshouldalteritsbehaviordependingonwhereitislocated.Atypicalusecaseistopreventthedevicefromworkingwhenitisoutsideofaparticularlocation.Forexample,ahospitalapplicationcouldrestrictaccesstopatientdatawhenitisnotatthehospital.Oryourapplicationmightwanttosilencenotificationswhenthedeviceisattheworkplace.LocationManagerhasamechanismcalledproximityalerts,andthereisasimilarrecentAPIcalledGeofencingApiforthenewerLocationServices.We’llbrieflydiscussthefirst,thenaddressthesecondindetail.

WementionedearlierthattheLocationManagercannotifyyouwhenthedeviceentersaspecifiedgeographicallocation.ThemethodtosetthisupisaddProximityAlert()fromtheLocationManagerclass.Basically,youtelltheLocationManagerthatyouwantanIntenttobefiredwhenthelocationofthedevicegoesinto,orleaves,acircleofacertainradiuswithacenteratalatitude/longitudeposition.TheIntentcantriggeraBroadcastReceiveroraServicetobecalled,oranActivitytobestarted.Thereisalsoanoptionaltimelimitplacedonthealert,soitcouldtimeoutbeforetheIntentfires.

Internally,thecodeforthismethodregisterslistenersforboththeGPSandnetworkprovidersandsetsuplocationupdatesforoncepersecondandaminDistanceof1meter.Youdon’thaveanywaytooverridethisbehaviororsetparameters.Therefore,ifyouleavethisrunningforalongtime,youcouldendupdrainingthebatteryveryquickly.Ifthescreengoestosleep,proximityalertswillonlybecheckedonceeveryfourminutes,butagain,youhavenocontroloverthetimedurationhere.Forthesereasons,wehave

includedademonstrationapplicationcalledProximityAlertDemowiththesampleapplications,butwewillnotdiveintothedetails.Instead,wewillturnourattentiontotheLocationServicesapproach,withanothersampleapplicationcalledGeofencingApi.NotethattheGeofencingApisampleapplicationwilllooksimilartotheFusedLocationProviderApisampleapplicationsincebothsharetheGoogleClientApimechanismforactivation.

TheGeofencingApiAPIAtthetimeofthiswriting,ageofenceisacircularregionwithalatitude/longitudecenter,plussometimeparameters.Atsomepointinthefuture,theregionmightnotbecircularbutfornowitis.Onceageofencehasbeenbuilt,itcanbepassedtotheGeofenceApiformonitoring.Yourapplicationcanevengoawayandyourgeofencecanbeactive.Alongwithageofence,orsetofgeofences,yourapplicationwillpassaPendingIntentwithanIntenttobefiredwhensomethinginterestinghappensaroundageofence.Thethreecurrenteventsareenter,exitanddwell.Enterandexitaresimpletounderstand;theIntentwillbefiredifthedevicegoesinto,orleaves,thecircularregion.ThedwelleventfirestheIntentafterthedeviceremainsinsideofthecircularregionforaperiodoftime.Thisloiteringdelayisspecifiedinmilliseconds.Andthat’sallthereistoit.

SeethesampleapplicationcalledGeofencingApiDemo.Itsetsuptwogeofencescalledhomeandwork,connectstoLocationServices,andregistersaserviceintenttobefiredwhenthedeviceenters,exitsordwellsineitherofthesegeofences.Whentriggered,theservicegeneratesanotificationpereventtomakeiteasierforyoutoseetheresults.Geofencesareoftenusedinthebackground,soaservicemakesalotofsensehere.Thatis,anapplicationshouldn’tneedtobeintheforegroundtohavegeofences.Infact,thebasicideaofageofenceisthatyouwantyourapplicationtobewakenedupifthedeviceentersorleavesaspecificgeographicregion.

ThesetupcodeusedearliertomakesurethatGooglePlayServicesandLocationServicesareavailableandreadyhasbeenleftoutofthissampleapplicationtomakeiteasiertofollowalong,butyouwouldwanttoincludethatcodeinaproductionapplication.Listing19-15showstheonCreate()methodofthemainactivity,inwhichthegeofencesandthePendingIntentarecreated.

Listing19-15.SettingUpGeofences

privateGoogleApiClientmClient=null;privateList<Geofence>mGeofences=newArrayList<Geofence>();privatePendingIntentpIntent=null;

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

finalfloatradius=0.5f*1609.0f;//halfmiletimes1609meterspermile

Geofence.Buildergb=newGeofence.Builder();//MakeahalfmilegeofencearoundyourhomeGeofencehome=gb.setCircularRegion(28.993818,-81.383816,radius).setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER|Geofence.GEOFENCE_TRANSITION_EXIT|Geofence.GEOFENCE_TRANSITION_DWELL).setExpirationDuration(12*60*60*1000)//12hours.setLoiteringDelay(300000)//5minutes.setRequestId("home").setNotificationResponsiveness(5000)//5secs.build();mGeofences.add(home);

//MakeanothergeofencearoundyourworkGeofencework=gb.setCircularRegion(28.36631,-81.52120,radius).setRequestId("work").build();mGeofences.add(work);Intentintent=newIntent(this,ReceiveTransitionsIntentService.class);

pIntent=PendingIntent.getService(getApplicationContext(),0,intent,PendingIntent.FLAG_UPDATE_CURRENT);

mClient=newGoogleApiClient.Builder(this,this,this).addApi(LocationServices.API).build();

Log.v(TAG,"Activity,clientarecreated");}

Seehowthegeofenceiscreatedasacirclearoundalat/lon,withtheeventsofinterest(inthiscaseallofthem)andsometimeparameters.Inthissample,thegeofenceswillbeactivefor12hours,oruntiltheyareremoved(seeonDestroy()).It’salsopossibletosetgeofencestoneverexpire.Theloiteringdelayof5minutesmeansthatthedwelleventwillfireifthedevicestaysinsidethegeofenceforatleast5minutes.TherequestIDwillbepassedbacktoyourapplicationwiththeIntentsoyoucanidentifywhichgeofencetheIntentisfor.Thenotificationresponsivenessof5secondsmeansthattheGeofencingApiwilltrytosendtheIntentwithin5secondsofwhentheeventhappens.However,therearenoguaranteesthattheIntentwillbethatquick.Thelargerthisvalue,thebetteritison

batterylife,sincetheAPIcouldsleepmoreandcheckthingslessoften.Ontheotherhand,ifthisvalueisverylong,forexampleseveralminutes,itispossibleyoumightevenmissaneventifthedevicepassesthroughyourgeofencequickly.Thechoiceofnotificationresponsivenesswilldependonhowbigyourgeofencesareandhowyouwantyourapplicationtobehave.

Similartotheprevioussampleapplication,aconnectionisattemptedfromonResume(),andListing19-16showswhatrunswhentheconnectionissuccessful.

Listing19-16.RegisteringGeofenceswiththeAPI

@OverridepublicvoidonConnected(Bundlearg0){//SetupgeofencesLog.v(TAG,"Settingupgeofences(onConnected)...");PendingResult<Status>pResult=mFencer.addGeofences(mClient,mGeofences,pIntent);pResult.setResultCallback(this);//ResultCallback<Status>interface}

@OverridepublicvoidonResult(Statusstatus){Log.v(TAG,"GotaresultfromaddGeofences("+status.getStatusCode()+"):"+status.getStatus().getStatusMessage());}

TheGeofencingApigetspassedtheAPIclienthandle,thelistofgeofences,andthePendingIntent.ThereturnisaPendingResult.Ifyouwanttofindoutiftheresultisultimatelysuccessfulornot,youneedtosetacallbackreceiverusingsetResultCallback().ThisactivityhasimplementedtheResultCallback<Status>interface,sotheonResult()callbackwillbeinvokedwiththeresultsoftheaddGeofences()methodcall.Forthissample,theresultissimplylogged,butofcourseyouwouldwanttotakestepsiftheresultwasnotsuccessful.That’sallthattheactivitydoes.NextupistheservicethatreceivesanIntentwhenaninterestingeventoccurs.

Listing19-17showstheinterestingcallbacksandmethodsoftheReceiveTransitionsIntentService,anIntentServiceforthisapplication.Itbasicallyreportsouttheinformationreceived,whetherthatisanerrororageofenceevent.Eventsaredisplayedusingnotifications.Thisisforyoursafetysincetheexpectationisthatyouwillstartthisapplicationathomeanddrivetowork.Wedonotwantyouhavingtowatchthedevice’sscreenduringthetrip.Instead,youwillbeabletoreviewallofthenotificationsfromeacheventwhenyouaresafelystopped.

Listing19-17.ReceivingIntentsfromtheGeofencingApi

publicvoidonCreate(){super.onCreate();notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);}

@OverrideprotectedvoidonHandleIntent(Intentintent){GeofencingEventgfEvent=GeofencingEvent.fromIntent(intent);//Firstcheckforerrorsif(gfEvent.hasError()){//GettheerrorcodewithastaticmethodinterrorCode=gfEvent.getErrorCode();//LogtheerrorLog.e(TAG,"LocationServiceserror:"+Integer.toString(errorCode));/**Ifthere'snoerror,getthetransitiontypeandtheIDs*ofthegeofenceorgeofencesthattriggeredthetransition*/}else{//Getthetypeoftransition(entryorexit)inttransitionType=gfEvent.getGeofenceTransition();StringtranTypeStr="UNKNOWN("+transitionType+")";switch(transitionType){caseGeofence.GEOFENCE_TRANSITION_ENTER:tranTypeStr="ENTER";break;caseGeofence.GEOFENCE_TRANSITION_EXIT:tranTypeStr="EXIT";break;caseGeofence.GEOFENCE_TRANSITION_DWELL:tranTypeStr="DWELL";break;}Log.v(TAG,"transitionTypereported:"+tranTypeStr);LocationtriggerLoc=gfEvent.getTriggeringLocation();Log.v(TAG,"triggeringlocationis"+triggerLoc);

List<Geofence>triggerList=gfEvent.getTriggeringGeofences();

String[]triggerIds=newString[triggerList.size()];

for(inti=0;i<triggerIds.length;i++){//GrabtheIdofeachgeofencetriggerIds[i]=triggerList.get(i).getRequestId();Stringmsg=tranTypeStr+":"+triggerLoc.getLatitude()+","+triggerLoc.getLongitude();Stringtitle=triggerIds[i];displayNotificationMessage(title,msg);}}}

privatevoiddisplayNotificationMessage(Stringtitle,Stringmessage){intnotif_id=(int)(System.currentTimeMillis()&0xFFL);

Notificationnotification=newNotificationCompat.Builder(this).setContentTitle(title).setContentText(message).setSmallIcon(android.R.drawable.ic_menu_compass).setOngoing(false).build();

notificationMgr.notify(notif_id,notification);}

Whenyoureplacethelatitudeandlongitudeofhomeandworkinthisapplication,yourunitonarealdevice,andyouthenmovethedevice,youwillseenotificationssuchasthoseinFigure19-8.

Figure19-8.NotificationsfromGeofencingApievents

Thefirsteventoccurredat6:40pmandhappenedbecausethedevicewasalreadyinsidethehomeregionwhentheappwasstarted.Thesecondeventat6:45pmisadwelleventbecausethedeviceisstillwithinthehomeregionaftertheloiteringdelayof5minutes.Hadthedeviceleftthehomeregionbeforethescreenshotwascaptured,therewouldhavebeenanexiteventfromhome.Notethatthelatitudeandlongitudeinthenotificationaretheactuallocationofthedeviceandnotnecessarilythecenteroftheregion.

ReferencesHerearehelpfulreferencesyoumaywishtoexplorefurther.

www.androidbook.com/proandroid5/projects.Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch19_Maps.zip.Thiszipfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneofthesezipfiles.Therearesomeextrasampleapplicationsinhere,includingWhereAmI4,whichcontainscustominfowindowsformarkers.

https://developer.android.com/guide/topics/location/index.htmlTheAndroiddeveloper’sguideforLocationandMaps.

https://developer.android.com/google/play-services/index.html.TheGooglePlayServicesdocumentationwhichincludestheFusedLocationProviderApi,GeofencingApiandGoogleMap.

https://developer.android.com/google/play-services/setup.html.InstructionsforincludingtheGoogle

PlayServiceslibraryintoyourapplication.Notethedrop-downmenutoallowchoosingbetweenAndroidStudioandEclipsewithADT.https://developers.google.com/maps/documentation/android/TheMapsAPIdocumentationwhichisseparatefromtherestoftheonlineAndroiddocumentation.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutmapssofar:

HowtogetyourownMapsAPIkeyfromGoogle.

MapFragment,themaincomponentforallmaps.

ThemodificationsyouneedtomaketoyourAndroidManifest.xmlfiletogetamapsapplicationtowork.

Definingalayouttocontainamap,andhowtoinstantiateamap.

Zoominginandout,panningandshowingthecurrentlocation.

Includingdifferentmodessuchassatelliteandtraffic.

Howmaptilesareusedtorendermaps.

Addingmarkerstoyourmaps.

Mapcamerasandmethodstosetazoomlevelthataccommodatesaspecificsetofmarkers.

TheGeocoder,andhowitconvertsfromaddresstolatitude/longitude,orfromlatitude/longitudetoaddressesandplacesofinterest.

PuttingtheGeocoderintoabackgroundthreadtoavoidnastyApplicationNotResponding(ANR)pop-ups.

TheLocationServicesservice,whichusesGPSand/ornetworktowerstopinpointthelocationofthedevice.

Selectingalocationprovider,andwhattodoifthedesiredlocationserviceorproviderisnotenabled.

Usingtheemulator’sfeaturestosendlocationeventstoyourapplicationfortesting.Thisincludesusingspecialfilesthatrecordentireseriesoflocationevents.

UsingmethodsoftheLocationclassto,forexample,calculatedistancesbetweenpoints.

HowtodoallofthechecksandcorrectiveactionstosetupGoogle

PlayServicesforLocationUpdates.

Alertingonproximity—thatis,settingupaproximityandbeingalertedwhenthedeviceentersorleavesthatproximity.

Settingupgeofencestoactonenter,exit,anddwelleventsforoneormoreregionswhileconservingbatterylife.

Chapter20

UnderstandingtheMediaFrameworksNowwearegoingtoexploreaveryinterestingpartoftheAndroidSDK:themediaframeworks.Wewillshowyouhowtoplayaudioandvideofromavarietyofsources.We’llalsocoverintheonlinecompanionsectionhowtotakephotoswiththecameraandrecordaudioandvideo.

UsingtheMediaAPIsAndroidsupportsplayingaudioandvideocontentundertheandroid.mediapackage.Inthischapter,wearegoingtoexplorethemediaAPIsfromthispackage.

Attheheartoftheandroid.mediapackageistheandroid.media.MediaPlayerclass.TheMediaPlayerclassisresponsibleforplayingbothaudioandvideocontent.Thecontentforthisclasscancomefromthefollowingsources:

Web:YoucanplaycontentfromtheWebviaaURL.

.apkfile:Youcanplaycontentthatispackagedaspartofyour

.apkfile.Youcanpackagethemediacontentasaresourceorasanasset(withintheassetsfolder).

TheStorageAccessFramework,newtoAndroidKitKat4.4,whichprovidesaccesstomediafilesstoredacrossarangeofprovidersandinternetservices.

SDcard:Youcanplaycontentthatresidesonthedevice’sSDcardoremulatedlocalstorage.

TheMediaPlayeriscapableofdecodingquiteafewdifferentcontentformats,including3rdGenerationPartnershipProject(3GPP,.3gp),MP3(.mp3),MIDI(.midandothers),OggVorbis(.ogg),PCM/WAVE(.wav),andMPEG-4(.mp4).RTSP,HTTP/HTTPSlivestreaming,andM3Uplaylistsarealsosupported,althoughplayliststhatincludeURLsarenot,atleastasofthiswriting.Foracompletelistofsupportedmediaformats,gotohttp://developer.android.com/guide/appendix/media-formats.html.

WhitherSDCards?Beforewediveintotheheartofthemediaframeworks,weshouldquicklyaddressthetopicofremovablestorage,andSDCardsinparticular.RecenttrendsinAndroiddevices

haveseensomemanufacturersdropthemfromdevices,whileotherscontinuetoincludethem.Googleitselfhasblurredthelinesofwhatisandisn’tremovalstoragebyobfuscatingthelow-levelfilesystemsinAndroid.

Regardlessofyourpersonalpreferenceasadeveloper,someofyouruserswilllikelystillhavedevicesthatsupportSDCardsandwanttousethem.Manyoftheexampleswe’llcoverhereareequallyapplicabletosourcingmediafilesfromSDCards.However,tosavespace,andspareyouunneededrepetition,we’veplacedsomeextraexamplesthatgointoSDCarddetailsandsupportingmaterialonthebook’swebsite.Besuretocheckitoutatwww.androidbook.com.

PlayingMediaTogetstarted,we’llshowyouhowtobuildasimpleapplicationthatplaysanMP3filelocatedontheWeb(seeFigure20-1).Afterthat,wewilltalkaboutusingthesetDataSource()methodoftheMediaPlayerclasstoplaycontentfromthe.apkfile.MediaPlayerisn’ttheonlywaytoplayaudio,though,sowe’llalsocovertheSoundPoolclass,aswellasJetPlayer,AsyncPlayer,and,forthelowestlevelofworkingwithaudio,theAudioTrackclass.Afterthat,wewilldiscusssomeoftheshortfallsoftheMediaPlayerclass.Finally,we’llseehowtoplayvideocontent.

PlayingAudioContentFigure20-1showstheuserinterfaceforourfirstexample.ThisapplicationwilldemonstratesomeofthefundamentalusesoftheMediaPlayerclass,suchasstarting,pausing,restarting,andstoppingthemediafile.Lookatthelayoutfortheapplication’suserinterface.

Figure20-1.Theuserinterfaceforthemediaapplication

TheuserinterfaceconsistsofaRelativeLayoutwithfourbuttons:onetostarttheplayer,onetopausetheplayer,onetorestarttheplayer,andonetostoptheplayer.WecouldhavemadethiseasyandjustcoupledourexamplewithaMediaControllerwidgetthatdoesthesamething,butwewanttoshowyoutheinnerworkingsofcontrollingthingsyourself.ThecodeandlayoutfilefortheapplicationareshowninListing20-1.We’regoingtoassumeyou’rebuildingagainstAndroid2.2orlaterforthisexample,becausewe’reusingthegetExternalStoragePublicDirectory()methodoftheEnvironmentclass.IfyouwanttobuildthisagainstanolderversionofAndroid,simplyusegetExternalStorageDirectory()insteadandadjustwhereyouputthemediafilessoyourapplicationwillfindthem.

NoteSeethe“References”sectionattheendofthischapterfortheURLfromwhichyoucanimporttheseprojectsintoEclipsedirectly,insteadofcopyingandpastingcode.

Listing20-1.TheLayoutandCodefortheMediaApplication

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"android:orientation="vertical">

<Buttonandroid:id="@+id/startPlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="StartPlayingAudio"

android:onClick="doClick"/>

<Buttonandroid:id="@+id/pausePlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="PausePlayer"

android:layout_below="@+id/startPlayerBtn"android:onClick="doClick"/>

<Buttonandroid:id="@+id/restartPlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="RestartPlayer"

android:layout_below="@+id/pausePlayerBtn"android:onClick="doClick"/>

<Buttonandroid:id="@+id/stopPlayerBtn"android:layout_width="match_parent"

android:layout_height="wrap_content"android:text="StopPlayer"

android:layout_below="@+id/restartPlayerBtn"android:onClick="doClick"/>

</RelativeLayout>

//ThisfileisMainActivity.javaimportandroid.app.Activity;importandroid.content.res.AssetFileDescriptor;importandroid.media.AudioManager;importandroid.media.MediaPlayer;importandroid.media.MediaPlayer.OnPreparedListener;importandroid.os.Bundle;importandroid.os.Environment;importandroid.util.Log;importandroid.view.View;

publicclassMainActivityextendsActivityimplementsOnPreparedListener{staticfinalStringAUDIO_PATH=

"http://www.androidbook.com/akc/filestorage/android/documentfiles/3389/play.mp3

privateMediaPlayermediaPlayer;privateintplaybackPosition=0;

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}

publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startPlayerBtn:try{//OnlyhaveoneoftheseplaymethodsuncommentedplayAudio(AUDIO_PATH);//playLocalAudio();//playLocalAudio_UsingDescriptor();}catch(Exceptione){e.printStackTrace();}break;

caseR.id.pausePlayerBtn:if(mediaPlayer!=null&&mediaPlayer.isPlaying()){playbackPosition=mediaPlayer.getCurrentPosition();mediaPlayer.pause();}break;caseR.id.restartPlayerBtn:if(mediaPlayer!=null&&!mediaPlayer.isPlaying()){mediaPlayer.seekTo(playbackPosition);mediaPlayer.start();}break;caseR.id.stopPlayerBtn:if(mediaPlayer!=null){mediaPlayer.stop();playbackPosition=0;}break;}}

privatevoidplayAudio(Stringurl)throwsException{killMediaPlayer();

mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(url);mediaPlayer.setOnPreparedListener(this);mediaPlayer.prepareAsync();}

privatevoidplayLocalAudio()throwsException{mediaPlayer=MediaPlayer.create(this,R.raw.music_file);mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//callingprepare()isnotrequiredinthiscasemediaPlayer.start();}

privatevoidplayLocalAudio_UsingDescriptor()throwsException{

AssetFileDescriptorfileDesc=getResources().openRawResourceFd(R.raw.music_file);if(fileDesc!=null){

mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(fileDesc.getFileDescriptor(),fileDesc.getStartOffset(),fileDesc.getLength());

fileDesc.close();

mediaPlayer.prepare();mediaPlayer.start();}}

//ThisiscalledwhentheMediaPlayerisreadytostartpublicvoidonPrepared(MediaPlayermp){mp.start();}

@OverrideprotectedvoidonDestroy(){super.onDestroy();killMediaPlayer();}

privatevoidkillMediaPlayer(){if(mediaPlayer!=null){try{mediaPlayer.release();}catch(Exceptione){e.printStackTrace();}}}}

Inthisfirstscenario,youareplayinganMP3filefromawebaddress.Therefore,youwillneedtoaddandroid.permission.INTERNETtoyourmanifestfile.Listing20-1showsthattheMainActivityclasscontainsthreemembers:afinalstringthatpointstotheURLoftheMP3file,aMediaPlayerinstance,andanintegermembercalledplaybackPosition.OuronCreate()methodjustsetsuptheuserinterfacefromourlayoutXMLfile.Inthebutton-clickhandler,whentheStartPlayingAudiobuttonis

pressed,theplayAudio()methodiscalled.IntheplayAudio()method,anewinstanceoftheMediaPlayeriscreated,andthedatasourceoftheplayerissettotheURLoftheMP3file.

TheprepareAsync()methodoftheplayeristhencalledtopreparetheMediaPlayerforplayback.We’reinthemainUIthreadofouractivity,sowedon’twanttotaketoolongtopreparetheMediaPlayer.Thereisaprepare()methodonMediaPlayer,butitblocksuntiltheprepareiscomplete.Ifthistakesalongtime,oriftheservertakesawhiletorespond,theusercouldthinktheapplicationisstuckor,worse,getanerrormessage.Thingslikeprogressdialogscanhelpyouruserunderstandwhatishappening.TheprepareAsync()methodreturnsimmediatelybutsetsupabackgroundthreadtohandletheprepare()methodoftheMediaPlayer.Whenthepreparationiscomplete,ouractivity’sonPrepared()callbackiscalled.ThisiswhereweultimatelystarttheMediaPlayerplaying.WehavetotelltheMediaPlayerwhothelistenerisfortheonPrepared()callback,whichiswhywecallsetOnPreparedListener()justbeforethecalltoprepareAsync().Youdon’thavetousethecurrentactivityasthelistener;wedoherebecauseit’ssimplerforthisdemonstration.

NowlookatthecodeforthePausePlayerandRestartPlayerbuttons.YoucanseethatwhenthePausePlayerbuttonisselected,yougetthecurrentpositionoftheplayerbycallinggetCurrentPosition().Youthenpausetheplayerbycallingpause().Whentheplayerhastoberestarted,youcallseekTo(),passinginthepositionobtainedearlierfromgetCurrentPosition(),andthencallstart().

TheMediaPlayerclassalsocontainsastop()method.Notethatifyoustoptheplayerbycallingstop(),youneedtopreparetheMediaPlayeragainbeforecallingstart()again.Conversely,ifyoucallpause(),youcancallstart()againwithouthavingtopreparetheplayer.Also,besuretocalltherelease()methodofthemediaplayeronceyouaredoneusingit.Inthisexample,youdothisaspartofthekillMediaPlayer()method.

ThereisasecondURLinthesampleapplicationsourcecodeforanaudiosource,butitisnotanMP3file,it’sastreamingaudiofeed(Radio-Mozart).ThisalsoworkswiththeMediaPlayerandshowsagainwhyyouneedtocallprepareAsync()insteadofprepare().Preparinganaudiostreamforplaybackcantakeawhile,dependingontheserver,networktraffic,andsoon.

Listing20-1showsyouhowtoplayanaudiofilelocatedontheWeb.TheMediaPlayerclassalsosupportsplayingmedialocaltoyour.apkfile.Listing20-2showshowtoreferenceandplaybackafilefromthe/res/rawfolderofyour.apkfile.Goaheadandaddtherawfolderunder/resifit’snotalreadythereintheEclipseproject.Then,copytheMP3fileofyourchoiceinto/res/rawwiththefilenamemusic_file.mp3.NotealsothecommentintheoriginalcodetouncommentthedesiredcalltoplayLocalAudio(),andcommentingoutplayAudio().

Listing20-2.UsingtheMediaPlayertoPlayBackaFileLocaltotheApplication

privatevoidplayLocalAudio()throwsException

{mediaPlayer=MediaPlayer.create(this,R.raw.music_file);mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//callingprepare()isnotrequiredinthiscasemediaPlayer.start();}

Ifyouneedtoincludeanaudioorvideofilewithyourapplication,youshouldplacethefileinthe/res/rawfolder.YoucanthengetaMediaPlayerinstancefortheresourcebypassingintheresourceIDofthemediafile.Youdothisbycallingthestaticcreate()method,asshowninListing20-2.NotethattheMediaPlayerclassprovidesafewotherstaticcreate()methodsthatyoucanusetogetaMediaPlayerratherthaninstantiatingoneyourself.InListing20-2,thecreate()methodisequivalenttocallingtheconstructorMediaPlayer(Contextcontext,intresourceId)followedbyacalltoprepare().Youshouldusethecreate()methodonlywhenthemediasourceislocaltothedevice,becauseitalwaysusesprepare()andnotprepareAsync().

UnderstandingthesetDataSourceMethodInListing20-2,wecalledthecreate()methodtoloadtheaudiofilefromarawresource.Withthisapproach,youdon’tneedtocallsetDataSource().Alternatively,ifyouinstantiatetheMediaPlayeryourselfusingthedefaultconstructor,orifyourmediacontentisnotaccessiblethrougharesourceIDoraURI,you’llneedtocallsetDataSource().

ThesetDataSource()methodhasoverloadedversionsthatyoucanusetocustomizethedatasourceforyourspecificneeds.Forexample,Listing20-3showshowyoucanloadanaudiofilefromarawresourceusingaFileDescriptor.

Listing20-3.SettingtheMediaPlayer’sDataSourceusingaFileDescriptor

privatevoidplayLocalAudio_UsingDescriptor()throwsException{AssetFileDescriptorfileDesc=getResources().openRawResourceFd(R.raw.music_file);if(fileDesc!=null){

mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(fileDesc.getFileDescriptor(),fileDesc.getStartOffset(),fileDesc.getLength());

fileDesc.close();

mediaPlayer.prepare();mediaPlayer.start();}}

Listing20-3assumesthatit’swithinthecontextofanactivity.Asshown,youcallthegetResources()methodtogettheapplication’sresourcesandthenusetheopenRawResourceFd()methodtogetafiledescriptorforanaudiofilewithinthe/res/rawfolder.YouthencallthesetDataSource()methodusingtheAssetFileDescriptor,thestartingpositiontobeginplayback,andtheendingposition.YoucanalsousethisversionofsetDataSource()ifyouwanttoplaybackaspecificportionofanaudiofile.Ifyoualwayswanttoplaytheentirefile,youcancallthesimplerversionofsetDataSource(FileDescriptordesc),whichdoesnotrequiretheinitialoffsetandlength.

Inthiscase,wechosetouseprepare()followedbystart(),onlytoshowyouwhatitmightlooklike.Weshouldbeabletogetawaywithitbecausetheaudioresourceislocal,butitwon’thurttouseprepareAsync()asbefore.

Wehaveonemoresourceforaudiocontenttotalkabout:theSDcard.RefertotheonlinecompanionchapterforthebasicsondealingwiththeSDcardanditsfilesystemcontents.Inourexample,weusedsetDataSource()toaccesscontentontheInternetbypassinginaURLforanMP3file.Ifyou’vegotanaudiofileonyourSDcard,youcanusethesamesetDataSource()methodbutinsteadpassitthepathtoyouraudiofileontheSDcard.Forexample,afilecalledmusic_file.mp3intheMusicdirectorycanbeplayedwiththeAUDIO_PATHvariablesetlikeso:

staticfinalStringAUDIO_PATH=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)+"/music_file.mp3";

YoumayhavenoticedthatwedidnotimplementonResume()andonPause()inourexample.Thismeansthatwhenouractivitygoesintothebackground,itcontinuestoplayaudio—atleast,untiltheactivityiskilled,oruntilaccesstotheaudiosourceisturnedoff.Forexample,ifwedonotholdawakelock,theCPUcouldbeshutdown,thusendingtheplayingofmusic.Manypeoplechoosetomanagemediaplaybackinaservicetoaidinworkingaroundtheseissues.Inourcurrentexample,additionalissuesincludeifMediaPlayerisplayinganaudiostreamoverWi-Fi,andifouractivitydoesnotobtainalockonWi-Fi,Wi-Ficouldbeturnedoff,andwe’llloseourconnectiontothestream.MediaPlayerhasamethodcalledsetWakeMode()thatallowsustosetaPARTIAL_WAKE_LOCKtokeeptheCPUalivewhileplaying.However,inordertolockWi-Fi,weneedtodothatseparatelythroughWifiManagerandWifiManager.WifiLock.

Theotheraspectofcontinuingtoplayaudiointhebackgroundisthatweneedtoknowwhennottodoso,perhapsbecausethere’sanincomingphonecall,orbecauseanalarmis

goingoff.AndroidhasanAudioManagertohelpwiththis.ThemethodstocallincluderequestAudioFocus()andabandonAudioFocus(),andthere’sacallbackmethodcalledonAudioFocusChange()intheinterfaceAudioManager.OnAudioFocusChangeListener.Formoreinformation,seetheMediapageintheAndroidDeveloper’sGuide.

UsingSoundPoolforSimultaneousTrackPlayingTheMediaPlayerisanessentialtoolinourmediatoolbox,butitonlyhandlesoneaudioorvideofileatatime.Whatifwewanttoplaymorethanoneaudiotracksimultaneously?OnewayistocreatemultipleMediaPlayersandworkwiththematthesametime.Ifyouonlyhaveasmallamountofaudiotoplay,andyouwantsnappyperformance,AndroidhastheSoundPoolclasstohelpyou.Behindthescenes,SoundPoolusesMediaPlayer,butwedon’tgetaccesstotheMediaPlayerAPI,justtheSoundPoolAPI.

OneoftheotherdifferencesbetweenMediaPlayerandSoundPoolisthatSoundPoolisdesignedtoworkwithlocalmediafilesonly.Thatis,youcanloadaudiofromresourcefiles,fileselsewhereusingfiledescriptors,orfilesusingapathname.ThereareseveralothernicefeaturesthatSoundPoolprovides,suchastheabilitytoloopanaudiotrack,pauseandresumeindividualaudiotracks,orpauseandresumeallaudiotracks.

TherearesomedownsidestoSoundPool,though.ThereisanoverallaudiobuffersizeinmemoryforallthetracksthatSoundPoolwillmanageofonly1MB.ThismightseemlargewhenyoulookatMP3filesthatareonlyafewkilobytesinsize.ButSoundPoolexpandstheaudioinmemorytomaketheplaybackfastandeasy.Thesizeofanaudiofileinmemorydependsonthebitrate,numberofchannels(stereoversusmono),samplerate,andlengthoftheaudio.IfyouhavetroublegettingyoursoundsloadedintoSoundPool,youcouldtryplayingwiththeseparametersofyoursourceaudiofiletomaketheaudiosmallerinmemory.

Ourexampleapplicationwillloadandplayanimalsounds.Oneofthesoundsisofcricketsanditplaysconstantlyinthebackground.Theothersoundsplayatdifferentintervalsoftime.Sometimesallyouheararecrickets;othertimesyouwillhearseveralanimalsallatthesametime.We’llalsoputabuttonintheuserinterfacetoallowforpausingandresuming.Listing20-4showsourlayoutXMLfileandtheJavacodeofouractivity.Yourbestbetistodownloadthisfromourwebsite,inordertogetthesoundfilesaswellasthecode.Seethe“References”sectionattheendofthischapterforinformationonhowtolocatethedownloadablesourcecode.

Listing20-4.PlayingAudiowithSoundPool

<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"

><ToggleButtonandroid:id="@+id/button"android:textOn="Pause"android:textOff="Resume"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="doClick"android:checked="true"/></LinearLayout>

//ThisfileisMainActivity.javaimportjava.io.IOException;importandroid.app.Activity;importandroid.content.Context;importandroid.content.res.AssetFileDescriptor;importandroid.media.AudioManager;importandroid.media.SoundPool;importandroid.os.Bundle;importandroid.os.Handler;importandroid.util.Log;importandroid.view.View;importandroid.widget.ToggleButton;

publicclassMainActivityextendsActivityimplementsSoundPool.OnLoadCompleteListener{privatestaticfinalintSRC_QUALITY=0;privatestaticfinalintPRIORITY=1;privateSoundPoolsoundPool=null;privateAudioManageraMgr;

privateintsid_background;privateintsid_roar;privateintsid_bark;privateintsid_chimp;privateintsid_rooster;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}

@OverrideprotectedvoidonResume(){soundPool=newSoundPool(5,AudioManager.STREAM_MUSIC,SRC_QUALITY);soundPool.setOnLoadCompleteListener(this);

aMgr=

(AudioManager)this.getSystemService(Context.AUDIO_SERVICE);

sid_background=soundPool.load(this,R.raw.crickets,PRIORITY);

sid_chimp=soundPool.load(this,R.raw.chimp,PRIORITY);sid_rooster=soundPool.load(this,R.raw.rooster,PRIORITY);sid_roar=soundPool.load(this,R.raw.roar,PRIORITY);

try{AssetFileDescriptorafd=this.getAssets().openFd("dogbark.mp3");sid_bark=soundPool.load(afd.getFileDescriptor(),0,afd.getLength(),PRIORITY);afd.close();}catch(IOExceptione){e.printStackTrace();}//sid_bark=soundPool.load("/mnt/sdcard/dogbark.mp3",PRIORITY);

super.onResume();}

publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.button:if(((ToggleButton)view).isChecked()){soundPool.autoResume();}else{soundPool.autoPause();}break;}}

@OverrideprotectedvoidonPause(){soundPool.release();soundPool=null;super.onPause();}

@Override

publicvoidonLoadComplete(SoundPoolsPool,intsid,intstatus){Log.v("soundPool","sid"+sid+"loadedwithstatus"+status);

finalfloatcurrentVolume=((float)aMgr.getStreamVolume(AudioManager.STREAM_MUSIC))/((float)aMgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC));

if(status!=0)return;if(sid==sid_background){if(sPool.play(sid,currentVolume,currentVolume,PRIORITY,-1,1.0f)==0)Log.v("soundPool","Failedtostartsound");}elseif(sid==sid_chimp){queueSound(sid,5000,currentVolume);}elseif(sid==sid_rooster){queueSound(sid,6000,currentVolume);}elseif(sid==sid_roar){queueSound(sid,12000,currentVolume);}elseif(sid==sid_bark){queueSound(sid,7000,currentVolume);}}

privatevoidqueueSound(finalintsid,finallongdelay,finalfloatvolume){newHandler().postDelayed(newRunnable(){@Overridepublicvoidrun(){if(soundPool==null)return;if(soundPool.play(sid,volume,volume,PRIORITY,0,1.0f)==0)Log.v("soundPool","Failedtostartsound("+sid+")");queueSound(sid,delay,volume);}},delay);}}

Thestructureofthisexampleisfairlystraightforward.WehaveauserinterfacewithasingleToggleButtononit.We’llusethistopauseandresumetheactiveaudio

streams.Whenourappstarts,wecreateourSoundPoolandloaditupwithaudiosamples.Whenthesamplesareproperlyloaded,westartplayingthem.Thecricketssoundplaysinaneverendingloop;theothersamplesplayafteradelayandthensetthemselvesuptoplayagainafterthesamedelay.Bychoosingdifferentdelays,wegetasomewhatrandomeffectofsoundsontopofsounds.

CreatingaSoundPoolrequiresthreeparameters:

ThefirstisthemaximumnumberofsamplesthattheSoundPoolwillplaysimultaneously.ThisisnothowmanysamplestheSoundPoolcanhold.

Thesecondparameteriswhichaudiostreamthesampleswillplayon.ThetypicalvalueisAudioManager.STREAM_MUSIC,butSoundPoolcanbeusedforalarmsorringtones.SeetheAudioManagerreferencepageforthecompletelistofaudiostreams.

TheSRC_QUALITYvalueshouldjustbesetto0whencreatingtheSoundPool.

Thecodedemonstratesseveraldifferentload()methodsofSoundPool.Themostbasicistoloadanaudiofilefrom/res/rawasaresource.Weusethismethodforthefirstfouraudiofiles.Thenweshowhowyoucouldloadanaudiofilefromthe/assetsdirectoryoftheapplication.Thisload()methodalsotakesparametersthatspecifytheoffsetandthelengthoftheaudiotoload.Thiswouldallowustouseasinglefilewithmultipleaudiosamplesinit,pullingoutjustwhatwewanttouse.Finally,weshowincommentshowyoumightaccessanaudiofilefromtheSDcard.UpthroughAndroid4.0,thePRIORITYparametershouldjustbe1.

Forourexample,wechosetousesomeofthefeaturesintroducedinAndroid2.2,specificallytheonLoadCompleteListenerinterfaceforouractivity,andtheautoPause()andautoResume()methodsinourbuttoncallback.

WhenloadingsoundsamplesintoaSoundPool,wemustwaituntiltheyareproperlyloadedbeforewecanstartplayingthem.WithinouronLoadComplete()callback,wecheckthestatusoftheload,and,dependingonwhichsounditis,wethensetituptoplay.Ifthesoundisthecrickets,weplaywithloopingturnedon(avalueof-1forthefifthparameter).Fortheothers,wequeuethesounduptoplayafterashortperiodoftime.Thetimevaluesareinmilliseconds.Notethesettingofthevolume.AndroidprovidestheAudioManagertoletusknowthecurrentvolumesetting.WealsogetthemaximumvolumesettingfromAudioManagersowecancalculateavolumevalueforplay()thatisbetween0and1(asafloat).Theplay()methodactuallytakesaseparatevolumevaluefortheleftandrightchannels,butwejustsetbothtothecurrentvolume.Again,PRIORITYshouldjustbesetto1.Thelastparameterontheplay()methodisforsettingtheplaybackrate.Thisvalueshouldbebetween0.5and2.0,with1.0beingnormal.

OurqueueSound()methodusesaHandlertobasicallysetupaneventintothefuture.OurRunnablewillrunafterthedelayperiodhaselapsed.WechecktobesurewestillhaveaSoundPooltoplayfrom,thenweplaythesoundonceandschedulethesamesoundtoplayagainafterthesameintervalasbefore.BecausewecallqueueSound()withdifferentsoundIDsanddifferentdelays,theeffectisasomewhatrandomplayingofanimalsounds.

Whenyourunthisexample,you’llhearcrickets,achimp,arooster,adog,andaroar(abear,wethink).Thecricketsareconstantlychirpingwhiletheotheranimalscomeandgo.OnenicethingaboutSoundPoolisthatitletsusplaymultiplesoundsatthesametimewithnorealworkonourpart.Also,we’renottaxingthedevicetoobadly,becausethesoundsweredecodedatloadtime,andwesimplyneedtofeedthesoundbitstothehardware.

Ifyouclickthebutton,thecricketswillstop,aswillanyotheranimalsoundcurrentlybeingplayed.However,theautoPause()methoddoesnotpreventnewsoundsfrombeingplayed.You’llheartheanimalsoundsagainwithinseconds(exceptforthecrickets).Becausewe’vebeenqueuingupsoundsintothefuture,wewillstillhearthosesounds.Infact,SoundPooldoesnothaveawaytostopallsoundsnowandinthefuture.You’llneedtohandlestoppingonyourown.Thecricketswillonlycomebackifweclickthebuttonagaintoresumethesounds.Buteventhen,wemighthavelostthecricketsbecauseSoundPoolwillthrowouttheoldestsoundtomakeroomfornewersoundsifthemaximumnumberofsimultaneouslyplayingsamplesisreached.

PlayingSoundswithJetPlayerSoundPoolisnottoobadaplayer,butthememorylimitationscanmakeitdifficulttogetthejobdone.AnalternativewhenyouneedtoplaysimultaneoussoundsisJetPlayer.Tailoredforgames,JetPlayerisaveryflexibletoolforplayinglotsofsoundsandforcoordinatingthosesoundswithuseractions.ThesoundsaredefinedusingMusicalInstrumentDigitalInterface(MIDI).

JetPlayersoundsarecreatedusingaspecialJETCreatortool.ThistoolisprovidedundertheAndroidSDKtoolsdirectory,althoughyou’llalsoneedtoinstallPythoninordertouseit,anditislimitedtotheMacOSXandWindowsSDKpackages.TheresultingJETfilecanbereadintoyourapplication,andthesoundssetupforplayback.Thewholeprocessissomewhatinvolvedandbeyondthescopeofthisbook,sowe’lljustpointyoutomoreinformationinthe“References”sectionattheendofthischapter.

PlayingBackgroundSoundswithAsyncPlayerIfallyouwantissomeaudioplayed,andyoudon’twanttotieupthecurrentthread,theAsyncPlayermaybewhatyou’relookingfor.TheaudiosourceispassedasaURItothisclass,sotheaudiofilecouldbelocalorremoteoverthenetwork.Thisclassautomaticallycreatesabackgroundthreadtohandlegettingtheaudioandstartingtheplayback.Becauseitisasynchronous,youwon’tknowexactlywhentheaudiowillstart.Norwillyouknowwhenitends,orevenifit’sstillplaying.Youcan,however,call

stop()togettheaudiotostopplaying.Ifyoucallplay()againbeforethepreviousaudiohasfinishedplaying,thepreviousaudiowillimmediatelystopandthenewaudiowillbeginatsometimeinthefuturewheneverythinghasbeensetupandfetched.Thisisaverysimpleclassthatprovidesanautomaticbackgroundthread.Listing20-5showshowyourcodeshouldlooktoimplementthis.

Listing20-5.PlayingAudiowithAsyncPlayer

privatestaticfinalStringTAG="AsyncPlayerDemo";privateAsyncPlayermAsync=null;

[...]

mAsync=newAsyncPlayer(TAG);mAsync.play(this,Uri.parse("file://”+“/perry_ringtone.mp3"),false,AudioManager.STREAM_MUSIC);

[...]

@OverrideprotectedvoidonPause(){mAsync.stop();super.onPause();}

Low-LevelAudioPlaybackUsingAudioTrackSofar,we’vebeendealingwithaudiofromfiles,betheylocalfilesorremotefiles.Ifyouwanttogetdowntoalowerlevel,perhapsplayingaudiofromastream,youneedtoinvestigatetheAudioTrackclass.Besidestheusualmethodslikeplay()andpause(),AudioTrackprovidesmethodsforwritingbytestotheaudiohardware.Thisclassgivesyouthemostcontroloveraudioplayback,butitismuchmorecomplicatedthantheaudioclassesdiscussedsofarinthischapter.OneofouronlinecompanionsampleapplicationsusestheAudioRecordclass.TheAudioRecordclassisverymuchliketheAudioTrackclass,sotogetabetterunderstandingoftheAudioTrackclass,refertotheAudioRecordsamplelateron.

MoreAboutMediaPlayerIngeneral,theMediaPlayerisverysystematic,soyouneedtocalloperationsinaspecificordertoinitializeaMediaPlayerproperlyandprepareitforplayback.ThefollowinglistsummarizessomeoftheotherdetailsyoushouldknowforusingthemediaAPIs:

OnceyousetthedatasourceofaMediaPlayer,youcannoteasilychangeittoanotherone—you’llhavetocreateanewMediaPlayerorcallthereset()methodtoreinitializethestateoftheplayer.

Afteryoucallprepare(),youcancallgetCurrentPosition(),getDuration(),andisPlaying()togetthecurrentstateoftheplayer.YoucanalsocallthesetLooping()andsetVolume()methodsafterthecalltoprepare().IfyouusedprepareAsync(),youshouldwaituntilonPrepared()iscalledbeforeusinganyoftheseothermethods.

Afteryoucallstart(),youcancallpause(),stop(),andseekTo().

EveryMediaPlayeryoucreateusesalotofresources,sobesuretocalltherelease()methodwhenyouaredonewiththemediaplayer.TheVideoViewtakescareofthisinthecaseofvideoplayback,butyou’llhavetodoitmanuallyifyoudecidetouseMediaPlayerinsteadofVideoView.MoreaboutVideoViewinthenextsections.

MediaPlayerworkswithseverallistenersyoucanuseforadditionalcontrolovertheuserexperience,includingOnCompletionListener,OnErrorListener,andOnInfoListener.Forexample,ifyou’remanagingaplaylistofaudio,OnCompletionListenerwillbecalledwhenapieceisfinishedsoyoucanqueueupthenextpiece.

Thisconcludesourdiscussionaboutplayingaudiocontent.Nowwe’llturnourattentiontoplayingvideo.Asyouwillsee,referencingvideocontentissimilartoreferencingaudiocontent.

PlayingVideoContentInthissection,wearegoingtodiscussvideoplaybackusingtheAndroidSDK.Specifically,wewilldiscussplayingavideofromawebserverandplayingonefromanSDcard.Asyoucanimagine,videoplaybackisabitmoreinvolvedthanaudioplayback.Fortunately,theAndroidSDKprovidessomeadditionalabstractionsthatdomostoftheheavylifting.

NotePlayingbackvideointheemulatorisnotveryreliable.Ifitworks,great.Butifitdoesn’t,tryrunningonadeviceinstead.Becausetheemulatormustuseonlysoftwaretorunvideo,itcanhaveaveryhardtimekeepingupwithvideo,andyouwilllikelygetunexpectedresults.

Playingvideorequiresmoreeffortthanplayingaudio,becausethere’savisualcomponenttotakecareofinadditiontotheaudio.Totakesomeofthepainaway,Androidprovidesaspecializedviewcontrolcalledandroid.widget.VideoViewthatencapsulatescreatingandinitializingtheMediaPlayer.Toplayvideo,youcreateaVideoView

widgetinyouruserinterface.YouthensetthepathorURIofthevideoandfirethestart()method.Listing20-6demonstratesvideoplaybackinAndroid.

Listing20-6.PlayingVideoUsingtheMediaAPIs

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="fill_parent"xmlns:android="http://schemas.android.com/apk/res/android">

<VideoViewandroid:id="@+id/videoView"android:layout_width="200px"android:layout_height="200px"/>

</LinearLayout>

//ThisfileisMainActivity.javaimportandroid.app.Activity;importandroid.net.Uri;importandroid.os.Bundle;importandroid.widget.MediaController;importandroid.widget.VideoView;

publicclassMainActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);this.setContentView(R.layout.main);

VideoViewvideoView=(VideoView)this.findViewById(R.id.videoView);MediaControllermc=newMediaController(this);videoView.setMediaController(mc);videoView.setVideoURI(Uri.parse("http://www.androidbook.com/akc/filestorage/android/+"documentfiles/3389/movie.mp4"));/*videoView.setVideoPath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/movie.mp4");*/videoView.requestFocus();videoView.start();}

}

Listing20-6demonstratesvideoplaybackofafilelocatedontheWebatwww.androidbook.com/akc/filestorage/android/documentfiles/3389/movie.mp4whichmeanstheapplicationrunningthecodewillneedtorequesttheandroid.permission.INTERNETpermission.AlloftheplaybackfunctionalityishiddenbehindtheVideoViewclass.Infact,allyouhavetodoisfeedthevideocontenttothevideoplayer.TheuserinterfaceoftheapplicationisshowninFigure20-2.

Figure20-2.ThevideoplaybackUIwithmediacontrolsenabled

Whenthisapplicationruns,youwillseethebuttoncontrolsalongthebottomofthescreenforaboutthreeseconds,andthentheydisappear.Yougetthembackbyclickinganywherewithinthevideoframe.Whenweweredoingplaybackofaudiocontent,weneededtodisplaythebuttoncontrolsonlytostart,pause,andrestarttheaudio.Wedidnotneedaviewcomponentfortheaudioitself.Withvideo,ofcourse,weneedbuttoncontrolsaswellassomethingtoviewthevideoin.Forthisexample,we’reusingaVideoViewcomponenttodisplaythevideocontent.Butinsteadofcreatingourownbuttoncontrols(whichwecouldstilldoifwechoseto),wecreateaMediaControllerthatprovidesthebuttonsforus.AsshowninFigure20-2andListing20-6,yousettheVideoView’smediacontrollerbycallingsetMediaController()toenabletheplay,pause,andseek-tocontrols.Ifyouwanttomanipulatethevideoprogrammaticallywithyourownbuttons,youcancallthestart(),pause(),stopPlayback(),andseekTo()methods.

Keepinmindthatwe’restillusingaMediaPlayerinthisexample—wejustdon’tseeit.Youcaninfact“play”videosdirectlyinMediaPlayer.IfyougobacktotheexamplefromListing20-1,putamoviefileonyourSDcard,andpluginthemovie’sfilepathinAUDIO_PATH,youwillfindthatitplaystheaudioquitenicelyeventhoughyoucan’tseethevideo.

WhereasMediaPlayerhasasetDataSource()method,VideoViewdoesnot.VideoViewinsteadusesthesetVideoPath()orsetVideoURI()methods.AssumingyouputamoviefileontoyourSDcard,youchangethecodefromListing20-6tocommentoutthesetVideoURI()callanduncommentthesetVideoPath()call,adjustingthepathtothemoviefileasnecessary.Whenyouruntheapplicationagain,youwillnowhearandseethevideointheVideoView.Technically,wecouldhavecalledsetVideoURI()withthefollowingtogetthesameeffectassetVideoPath():

videoView.setVideoURI(Uri.parse("file://"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/movie.mp4"));

YoumighthavenoticedthatVideoViewdoesnothaveamethodtoreaddatafromafiledescriptorasMediaPlayerdid.YoumayalsohavenoticedthatMediaPlayerhasacoupleofmethodsforaddingaSurfaceHoldertoaMediaPlayer(aSurfaceHolderislikeaviewportforimagesorvideo).OneoftheMediaPlayermethodsiscreate(Contextcontext,Uriuri,SurfaceHolderholder),andtheotherissetDisplay(SurfaceHolderholder).

BonusOnlineChapteronRecordingandAdvancedMediaNowthatyouhavemasteredmanyoftheaspectsofmediaplayback,includingthevarietyofmethodstobuildyourownaudioandvideocapabilitiesintoyourapplication,thereareafewmoreareastoexploreonthetopicthatarealmostabook’sworthofcontentintheirownright.Sowehaveputthemtogetherintoanotherbonusonlinechapterthatexploresthefollowing:

AudiorecordingwithMediaRecorder,AudioRecord,andothertechniques

Videorecordingfromthegroundup

Cameraandcamcorderprofilesforvideorecording

UsingintentsandtheMediaStoreclasstohaveotherapplicationsdoallyourrecordingforyou!

TakealookattheonlinematerialfortheAudioandVideoRecordingbonuschapter.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Fortheprojectsinthischapter,lookforazipfilecalledProAndroid5_Ch20_Media.zip.Thiszipfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoEclipsefromoneofthesezipfiles.

http://developer.android.com/guide/topics/media/jet/jetcreator_manual.htmlTheusermanualfortheJETCreatortool.YoucanusethistocreateaJETsoundfiletobeplayedusingtheJetPlayer.JETCreatorisonlyavailableforWindowsandMacOS.ToseeJetPlayerinaction,loadtheJetBoysampleprojectfromtheAndroidSDKintoEclipse,buildit,andrunit.NotethattheFirebuttonisthecenterdirectionalpadkey.

SummaryHereisasummarythetopicscoveredinthismediachapteronaudioandvideo:

PlayingaudiothroughaMediaPlayer

SeveralwaystosourceaudioforMediaPlayer,fromlocalapplicationresources,tofiles,tostreamingoverthenetwork

StepstotakewithaMediaPlayertogettheaudiotocomeoutproperly

SoundPoolanditsabilitytoplayseveralsoundssimultaneously

SoundPool’slimitationsintermsoftheamountofaudioitcanhandle

AsyncPlayer,whichisusefulbecausesoundsgenerallyneedtobemanagedinthebackground

AudioTrack,whichprovideslow-levelaccesstoaudioPlayingvideousingVideoView

Chapter21

HomeScreenWidgetsHomescreenwidgetsinAndroidpresentfrequentlychanginginformationonthehomescreenofAndroid.Homescreenwidgetsaredisconnectedviewsdisplayedonthehomescreen.Datacontentoftheseviewsisupdatedatregularintervalsbybackgroundprocessesorjustkeptasastaticview.

Forexample,ane-mailhomescreenwidgetmightalertyoutothenumberofoutstandinge-mailstoberead.Thewidgetmayjustshowyouthenumberofe-mailsandnotthemessagesthemselves.Clickingthee-mailcountmaythentakeyoutotheactivitythatdisplaysactuale-mails.Thesecouldevenbeexternale-mailsourcessuchasYahoo,Gmail,andHotmail,aslongasthedevicehasawaytoaccessthecountsthroughHTTPorotherconnectivitymechanisms.

IntheAndroidSDKawidgetisdeclarativelydefined.Awidgetdefinitioncontainsthefollowing:

Aviewlayouttobedisplayedonthehomescreen,alongwithhowbigitshouldbetofitonahomepage.

Atimerthatspecifiesthefrequencyofupdates.

AbroadcastreceiverJavaclasscalledawidgetproviderthatcanrespondtotimerupdatesinordertoaltertheviewinsomefashionbypopulatingwithdata.

Anactivityclassthatisresponsibleforcollectingtheinputnecessarytofurtherconfigurethewidgettobedisplayed.

Thetimer,thereceiver,andtheconfigurationactivityareoptional.OnceawidgetisdefinedandtheJavaclassesareprovided,thewidgetwillbeavailablefortheusertodragontoahomepage.TheviewandthecorrespondingJavaclassesarearchitectedinsuchawaythattheyaredisconnectedfromeachother.Forexample,anyAndroidserviceoractivitycanretrievetheviewusingitslayoutID,populatethatviewwithdata(justlikepopulatingatemplate),andsendittothehomescreen.Oncetheviewissenttothehomescreen,itisdislodgedfromtheunderlyingJavacode.

Beforeweshowyouhowtoimplementawidget,we’llfirstgiveyouanoverviewofhowawidgetisusedbyanenduser.

UserExperiencewithHomeScreenWidgetsHomescreenwidgetfunctionalityinAndroidallowsyoutochooseapreprogrammed

widgettobeplacedonthehomescreen.Whenplaced,thewidgetwillallowyoutoconfigureitusinganactivity(definedaspartofthewidgetpackage),ifnecessary.Itisimportanttounderstandthisinteractionbeforeactuallygoingintothedetailsofhowawidgetisimplemented.

WearegoingtowalkyouthroughawidgetcalledBirthdayWidgetthatwehavecreatedforthischapter.Wewillpresentthesourcecodeforitlaterinthechapter.First,wearegoingtousethiswidgetasanexampleforourwalkthrough.Asaconsequenceofsourcecodecominglater,weneedyourconsiderationtoreadalongandfollowthepicturesandnotlookforthiswidgetonyourscreen.Ifyoufollowtheprovidedfiguresandexplanation,youwillknowthenatureandbehavioroftheBirthdayWidget,whichwillmakethingsclearwhenwecodeitsubsequently.

Let’sstartthistourbylocatingthewidgetwewantandcreatinganinstanceofitonthehomescreen.ThewayyouaccesstheavailablewidgetlistisdifferentdependingontheAndroidrelease.Usuallythough,thelistofwidgetsiskeptalongsidethelistofapplicationsavailableonyourdevice.HereisanexamplefromAPI16(orJellybeanversionofAndroid)inFigure21-1.

Figure21-1.Homescreenwidgetpicklist

InthelistofwidgetsinFigure21-1,theBirthdayWidgetisdesignedforthischapter.Ifyouchoosethiswidget,Androidallowsyoutodragittooneofpagesofyourhome

screen.AndroidwillcreateacorrespondingwidgetinstanceonthehomescreenthatlooksliketheexampleBirthdayWidgetshowninFigure21-2.

Figure21-2.AnexampleBirthdayWidget

BirthdayWidgetinFigure21-2willindicateinitsheaderthenameoftheperson,howmanydaysawaythisperson’sbirthdayis,whenthedateofbirthfallsthisyear,andalinktobuygifts.Youmaybewonderinghowthenameofthepersonanddateofbirthwereconfigured.Whatifyouwanttwoinstancesofthiswidget,eachwiththenameanddateofbirthforadifferentperson?Thisiswherethewidgetconfigurationactivitycomesintoplayandisthetopicwearecoveringnext.

UnderstandingWidgetConfigurationActivityAwidgetdefinitionoptionallyincludesaspecificationofanactivitycalledawidgetconfigurationactivity.Whenyouchooseawidgetfromthehomepagewidgetpicklisttocreatethewidgetinstance,Androidinvokesthecorrespondingwidgetconfigurationactivityifoneisdefinedforit.Thisactivityissomethingyouneedtocode.

IncaseofourBirthdayWidget,thisconfigurationactivitywillpromptyouforthenameofthepersonandtheupcomingbirthdate,asshowninFigure21-3.Itistheresponsibilityoftheconfigurationactivitytosavethisinformationinapersistentplaceso

thatwhenanupdateiscalledonthewidgetprovider,thewidgetproviderwillbeabletolocatethisinformationandupdatethenumberofdaysuntilthebirthday.

Figure21-3.BirthdayWidgetconfigurationactivity

NoteWhenauserchoosestocreatetwoBirthdayWidgetinstancesonthehomescreen,theconfigurationactivitywillbecalledtwice(onceforeachwidgetinstance).

Internally,AndroidkeepstrackofthewidgetinstancesbyallocatingthemuniqueIDs.ThisuniquewidgetinstanceIDispassedtotheJavacallbacksandtotheconfiguratorJavaclasssothatinitialconfigurationandupdatescanbedirectedtotherightinstanceofthewidgetonthehomepage.InFigure21-2,inthelaterpartofthestringsatya:3,the3isthewidgetinstanceID.

UnderstandingtheLifeCycleofaWidgetThelifecycleofawidgethasthefollowingphases:

1. Widgetdefinition

2. Widgetinstancecreation

3. onUpdate()(whenthetimeintervalexpires)

4. Responsestoclicks(onthewidgetviewonthehomescreen)

5. Widgetdeletion(fromthehomescreen)

6. Uninstallation

Wewillgothroughthesephasesindetailnow.

UnderstandingWidgetDefinitionPhaseWidgetdefinitionstartswiththedefinitionofthewidgetproviderclassintheAndroidmanifestfile.Listing21-1showsthedefinitionfortheAppWidgetProviderthatwehavedesignedforthischaptercalledBDayWidgetProviderinthemanifestfile.

Listing21-1.WidgetDefinitioninAndroidManifestFile

<!--filename:AndroidManifest.xml,project:ProAndroid5_ch21_TestWidgets.zip--><manifest..><application>....<receiverandroid:name=".BDayWidgetProvider">

<meta-dataandroid:name="android.appwidget.provider"

android:resource="@xml/bday_appwidget_provider"/>

<intent-filter>

<actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"

/>

</intent-filter></receiver>...<activity>.....</activity></application></manifest>

ThisdefinitionindicatesthatthereisabroadcastreceiverJavaclasscalledBDayWidgetProviderwhichreceivesapplicationwidgetbroadcastupdatemessages.ThewidgetclassdefinitioninListing21-1alsopointstoanXMLfile@xml/bday_appwidget_providerwhichis/res/xml/bday_appwidget_provider.xml.ThisXMLfileisinListing21-2.Thiswidgetdefinitionfilehasanumberofthingsaboutthiswidgetsuchasitslayoutresourcefile,updatefrequency,etc.

Listing21-2.WidgetViewDefinitioninWidgetProviderInformationXMLFile

<!--/res/xml/bday_appwidget_provider.xml(ProAndroid5_ch21_TestWidgets.zip)--><appwidget-provider

xmlns:android="http://schemas.android.com/apk/res/android"

android:minWidth="150dp"android:minHeight="120dp"android:updatePeriodMillis="43200000"android:initialLayout="@layout/bday_widget"android:configure="com.androidbook.BDayWidget.ConfigureBDayWidgetActivity"android:resizeMode="horizontal|vertical"android:previewImage="@drawable/some_preview_image_icon"></appwidget-provider>

ThisXMLfileiscalledtheAppwidgetproviderinformationfile.Internally,thisgetstranslatedtotheAppWidgetProviderInfoJavaclass.Thisfileidentifiesthewidthandheightofthelayouttobe150dpand120dp,respectively.Thisdefinitionfilealsoindicatestheupdatefrequencytobe12hourstranslatedtomilliseconds.ThewidgetdefinitionalsopointstoalayoutfilethroughtheinitialLayoutattribute.Thislayoutfile(seefutureListing21-6)producesthewidgetlookthatisshowninFigure21-2.

UnderstandingresizeModeAttributeStartingwithSDK3.1,usershavetheabilitytoresizeawidgetthatisplacedononeoftheirimages.Theuserseesresizehandleswhentheylong-clickthewidgetandcanthenusethesehandlestoresize.Thisresizecanbehorizontal,vertical,ornone.Youcancombinehorizontalandverticaltoresizethewidgetinbothdimensions,asshowninListing21-2.However,totakeadvantageofthis,yourwidgetcontrolsshouldbelaidoutinsuchawaythattheycanexpandandcontractusingtheirlayoutparameters.Thereisnocallbacktotellyouwhatsizeyourwidgetis.

UnderstandingpreviewImageAttributeThepreviewimageattributeinListing21-2indicateswhatimageoriconisusedtoshowyourwidgetinthelistofavailablewidgets.Ifyouomitit,thedefaultbehavioristoshowthemainiconforyourapplicationpackage,whichisindicatedinthemanifestfile.

UnderstandingWidgetLayout:initialLayoutAttributeThelayoutforwidgetviewsisrestrictedtocontainonlycertaintypesofviewelements.TheviewsallowedinawidgetlayoutareexposedthroughaninterfacecalledRemoteViews,andonlycertainviewscanbecomposedintothislayout.SomeoftheallowedviewelementsareshowninListing21-3.Notethattheirsubclassesarenotsupported—onlythosethatareincludedinListing21-3.

Listing21-3.AllowedViewControlsinRemoteViews

FrameLayoutLinearLayoutRelativeLayoutGridLayout

AnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackViewAdapterViewFlipper

Thislistmaygrowwitheachrelease.Theprimaryreasonforrestrictingwhatisallowedinaremoteviewisthattheseviewsaredisconnectedfromtheprocessesthatactuallycontrolthem.ThesewidgetviewsarehostedbyanapplicationliketheHomeapplication.Thecontrollersfortheseviewsarebackgroundprocessesthatgetinvokedbytimers.Forthisreason,theseviewsarecalledremoteviews.ThereisacorrespondingJavaclasscalledRemoteViewsthatallowsaccesstotheseviews.Inotherwords,programmersdonothavedirectaccesstotheseviewstocallmethodsonthem.YouhaveaccesstotheseviewsonlythroughtheRemoteViews(likeagatekeeper).

WewillcovertherelevantmethodsofaRemoteViewsclasswhenweexploretheexampleinthenextmainsection.Fornow,rememberthatonlyalimitedsetofviewsinListing21-3areallowedinthewidgetlayoutfile.

UnderstandingconfigureAttributeThewidgetdefinition(Listing21-2)usestheconfigureattributetospecifytheconfigurationactivitythatneedstobeinvokedwhentheusercreatesawidgetinstance.ThisconfigurationactivityspecifiedinListing21-2istheConfigureBDayWidgetActivity.Thisactivity(Figure21-3)islikeanyotherAndroidactivity.Formfieldsonthisactivityareusedtocollecttheinformationneededbyawidgetinstance.

UnderstandingWidgetInstanceCreationPhaseWhenauserchoosesawidgettocreateawidgetinstance,Androidinvokestheconfigurationactivity(Figure21-3)ifitisdefinedintheconfigurationXMLfileforthewidget.Ifthisconfigurationactivityisnotdefinedthenthisphaseskippedandthewidgetispresenteddirectlyonthehomepage.Wheninvokedthisconfigurationactivitydoesthefollowing:

1. ReceivethewidgetinstanceIDfromtheinvokingintentthatstartedtheconfigurationactivity.

2. Prompttheuserthroughformfieldstocollectthewidget-instance–specificinformation.

3. PersistthewidgetinstanceinformationsothatsubsequentcallstoAppWidgetProvider’sonUpdatemethodhaveaccesstothisinformation.

4. PreparetodisplaythewidgetviewforthefirsttimebyretrievingthewidgetviewlayoutandcreateaRemoteViewsobjectwithit.

5. CallmethodsontheRemoteViewsobjecttosetvaluesonindividualviewobjects,suchastextandimages.

6. AlsousetheRemoteViewsobjecttoregisteranyonClickeventsonanyofthesubviewsofthewidget.

7. TelltheAppWidgetManagertopainttheRemoteViewsonthehomescreenusingtheinstanceIDofthatwidget.

8. ReturnthewidgetID,andclose.

NoticethatthefirstpopulationofthewidgetinthiscaseisdonebytheconfigurationactivityandnotAppWidgetProvider’sonUpdate()method.

NoteTheconfigurationactivityisoptional.Iftheconfigurationactivityisnotspecified,thecallgoesdirectlytotheonUpdate()methodoftheAppWidgetProvider.ItisuptoonUpdate()toupdatetheview.

Androidwillundertakethisprocessforeachwidgetinstancethattheusercreates.Besidesinvokingtheconfigurationactivity,AndroidalsoinvokestheonEnabledcallbackoftheAppWidgetProvider.Let’sbrieflyconsiderthecallbacksonanAppWidgetProviderclassbytakingalookattheshellofourBDayWidgetProvider(seeListing21-4).WewillexaminethecompletelistingofthisfilelaterinListing21-10.

Listing21-4.AWidgetProviderShell

//filename:BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)publicclassBDayWidgetProviderextendsAppWidgetProvider{

publicvoidonUpdate(Contextcontext,AppWidgetManagerappWidgetManager,int[]appWidgetIds){}publicvoidonDeleted(Contextcontext,int[]appWidgetIds){}publicvoidonEnabled(Contextcontext){}publicvoidonDisabled(Contextcontext){}}

TheonEnabled()callbackmethodindicatesthatthereisatleastoneinstanceofthewidgetupandrunningonthehomescreen.Thismeansausermusthavedroppedthewidgetonthehomepageatleastonce.Inthiscall,youwillneedtoenablereceivingmessagesforthisbroadcastreceivercomponent(youwillseethisinListing21-10).The

SDKbaseclassAppWidgetProviderhasthefunctionalitytoenableordisablereceivingbroadcastmessages.

TheonDeleted()callbackmethodiscalledwhenauserdragsthewidgetinstanceviewtothetrashcan.Thisiswhereyouwillneedtodeleteanypersistentvaluesyouareholdingforthatwidgetinstance.

TheonDisabled()callbackmethodiscalledafterthelastwidgetinstanceisremovedfromthehomescreen.Thishappenswhenauserdragsthelastinstanceofawidgettothetrash.Youshouldusethismethodtounregisteryourinterestinreceivinganybroadcastmessagesintendedforthiscomponent(youwillseethisinListing21-9).

TheonUpdate()callbackmethodiscalledeverytimethetimerspecifiedinListing21-2expires.Thismethodisalsocalledtheveryfirsttimethewidgetinstanceiscreatedifthereisnoconfigurationactivity.Ifthereisaconfigurationactivity,thismethodisnotcalledatthecreationofawidgetinstance.Thismethodwillsubsequentlybecalledwhenthetimerexpiresatthefrequencyindicated.

UnderstandingonUpdatePhaseOncethewidgetinstanceisonthehomescreen,thenextsignificanteventistheexpirationofthetimer.AndroidwillcallonUpdate()inresponsetothattimer.BecauseonUpdate()iscalledisthroughabroadcastreceiver,thecorrespondingJavaprocesswillbeloadedandwillremainliveuntiltheendofthatcall.Oncethecallreturns,theprocesswillbereadytobetakendown.

OnceyouhavethenecessarydataavailabletoupdatethewidgetintheonUpdate()method,youcaninvoketheAppWidgetManagertopainttheremoteview.ThisgoestoshowthattheAppWidgetProviderclassisstatelessandmayevenbeincapableofmaintainingstaticvariablesbetweeninvocations.ThisisbecausetheJavaprocesscontainingthisbroadcastreceiverclasscouldbetakendownandreconstructedbetweentwoinvocations,resultinginre-initializationofstaticvariables.

Asaresult,youwillneedtocomeupwithaschemetorememberstateifthatisrequired.Youcansavethestateofthewidgetinstanceinapersistentstoresuchasafile,sharedpreferences,oraSQLitedatabase.Intheexamplesinthischapter,weusedsharedpreferencesasthepersistenceAPI.

CautionTosavepower,Googlerecommendsthatthedurationoftheupdatesbemorethananhour,sothedevicewon’twakeuptoooften.Startingwiththe2.0API,thereisarestrictionof30minutesormorefortheupdatetimeout.

Fordurationsthatareshorter,suchasonlyseconds,youneedtocallthisonUpdate()methodyourselfbyusingthefacilitiesintheAlarmManagerclass.WhenyouuseAlarmManager,youalsohavetheoptionnottocallonUpdate()but,instead,dotheworkofonUpdate()inalarmcallbacks.RefertoChapter17forworkingwiththealarmmanager.

ThisiswhatyoutypicallyneedtodoinanonUpdate()method:

1. Makesuretheconfiguratorhasfinisheditswork;otherwise,justreturn.Thisshouldnotbeprobleminreleases2.0andabove,wherethedurationisexpectedtobelonger.Otherwise,basedontheupdateinterval(whenitistoosmall)itispossiblethatonUpdate()willbecalledbeforetheuserhasfinishedconfiguringthewidgetintheconfigurator.

2. Retrievethepersisteddataforthatwidgetinstance.

3. Retrievethewidgetviewlayout,andcreateaRemoteViewsobjectwithit.

4. CallmethodsontheRemoteViewstosetvaluesonindividualviewobjectssuchastextandimages.

5. RegisteranyonClickeventsonanyoftheviewsbyusingpendingintents.

6. TelltheAppWidgetManagertopainttheupdatedRemoteViewsusingtheinstanceID.

Asyoucansee,thereisalotofoverlapbetweenwhataconfiguratordoesinitiallyandwhattheonUpdate()methoddoes.Youmaywanttoreusethisfunctionalitybetweenthetwoplaces.

UnderstandingWidgetViewMouseClickEventCallbacksAsstated,theonUpdate()methodkeepsthewidgetviewsuptodate.Thewidgetviewandsubelementsinthatviewcouldhavecallbacksregisteredforamouseclick.Typically,theonUpdate()methodusesapendingintenttoregisteranactionforaneventlikeamouseclick.Thisactioncouldthenstartaserviceorstartanactivitysuchasopeningupabrowser.

Thisinvokedserviceoractivitycanthencommunicatebackwiththeview,ifneeded,usingthewidgetinstanceIDandtheAppWidgetManager.Hence,itisimportantthatthependingintentcarrieswithitthewidgetinstanceID.

DeletingaWidgetInstanceAnotherdistincteventthatcanhappentoawidgetinstanceisthatitcangetdeleted.Todothis,auserhastolong-pressthewidgetonthehomescreen.Thiswillenablethetrashcantoshowonthehomescreen.Theusercanthendragthewidgetinstancetothetrashcantodeletethewidgetinstancefromthescreen.

DoingsocallstheonDelete()methodofthewidgetprovider.Ifyouhavesavedanystateinformationforthiswidgetinstance,youwillneedtodeletethatdatainthisonDeletemethod.

AndroidalsocallsonDisable()ifthewidgetinstancethathasjustbeendeletedisthelastofthewidgetinstancesofthistype.Youwillusethiscallbacktocleanupanypersistenceattributesthatarestoredforallwidgetinstancesandalsounregisterfor

callbacksfromthewidgetonUpdate()broadcasts.

UninstallingWidgetPackagesThereisaneedtocleanupthewidgetsifyouareplanningtouninstallandinstallanewreleaseofyour.apkfilecontainingthesewidgets.

Itisrecommendedthatyouremoveordeleteallwidgetinstancesbeforetryingtouninstallthepackage.Followthedirectionsinthe“DeletingaWidgetInstance”sectiontodeleteeachwidgetinstanceuntilnoneremain.

Then,youcanuninstallandinstallthenewrelease.ThisisespeciallyimportantifyouareusingtheEclipseADTtodevelopyourwidgets,becauseduringthedevelopmenttime,ADTtriestodothiseverytimeyouruntheapplication.So,betweenruns,makesureyouremovethewidgetinstances.

ImplementingASampleWidgetApplicationSofar,wehavecoveredthetheoryandapproachbehindwidgets.Let’screatethesamplewidgetwhosebehaviorhasbeenusedastheexampletoexplainwidgetarchitecture.Wewilldevelop,test,anddeploythisBirthdayWidget.

EachBirthdayWidgetinstancewillshowaname,thedateofthenextbirthday,andhowmanydaysfromtodayuntilthebirthday.ItwillalsocreateanonClickareawhereyoucanclicktobuygifts.Thisclickwillopenabrowserandtakeyoutowww.google.com.

ThelayoutofthefinishedwidgetshouldlooklikeFigure21-4.

Figure21-4.BirthdayWidgetlookandfeel

Theimplementationofthiswidgetconsistsofthefollowingwidget-relatedfiles.TheentireprojectisalsoavailablefordownloadattheURLmentionedinthe“References”sectionofthischapter.

Thebasicfilesare

AndroidManifest.xml:WheretheAppWidgetProviderisdefined(seeListing21-5)

res/xml/bday_appwidget_provider.xml:Widget

dimensionsandlayout(seeListing21-2)

res/layout/bday_widget.xml:Thewidgetlayout(seeListing21-6)

res/drawable/box1.xml:Providesboxesforsectionsofthewidgetlayout(seeListing21-7)

src/…/BdayWidgetProvider.java:ImplementationoftheAppWidgetProviderclass(seeListing21-10)

Thesefilesimplementthewidgetconfigurationactivity:

src/…/ConfigureBDayWidgetActivity.java:Configurationactivity(seeListing21-8)

layout/edit_bday_widget.xml:Layoutfortakingthenameandbirthday(seeListing21-9)

Thesefilesstore/retrievethestateofawidgetinstanceusingpreferences:

src/…/IWidgetModelSaveContract.java:Contractforsavingandretrievingawidget’sdata(Seeindownloadableproject)

src/…/APrefWidgetModel.java:Abstractpreference-basedwidgetmodelthatsaveswidgetdatainpreferences(seeindownloadableproject)

src/…/BDayWidgetModel.java:Widgetmodelholdingthedataforawidgetview(seeindownloadableproject)

src/…/Utils.java:Afewutilityclasses(seeindownloadableproject)

Wewillwalkthroughsomeofthekeyfilesandexplainanyadditionalconceptsthatbearfurtherconsideration.Youcangettherestofthefilesfromthedownloadableprojectforthischapter.

DefiningtheWidgetProviderFortheBirthdayWidgetprojectthemanifestfileisinListing21-5.IthasthedeclarationsforthewidgetproviderBDayAppWidgetProviderasabroadcastreceiverandalsothedefinitionfortheconfigurationactivityConfigureBDayWidgetActivity.NoticehowthewidgetproviderdefinitionalsopointstothewidgetdefinitionXMLfile@xml/bday_appwidget_provider.

Listing21-5.AndroidManifestFileforBDayWidgetSampleApplication

<?xmlversion="1.0"encoding="utf-8"?><!--file:

AndroidManifest.xml(ProAndroid5_ch21_TestWidgets.zip)--><manifestxmlns:android="http://schemas.android.com/apk/res/android"

package="com.androidbook.BDayWidget"android:versionCode="1"android:versionName="1.0.0"><applicationandroid:icon="@drawable/icon"android:label="BirthdayWidget"><!--***********************************************************************BirthdayWidgetProviderReceiver**********************************************************************--><receiverandroid:name=".BDayWidgetProvider"><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/bday_appwidget_provider"/><intent-filter><actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/></intent-filter></receiver><!--***********************************************************************BirthdayProviderConfigurationactivity**********************************************************************--><activityandroid:name=".ConfigureBDayWidgetActivity"android:label="ConfigureBirthdayWidget"><intent-filter><actionandroid:name="android.appwidget.action.APPWIDGET_CONFIGURE"/></intent-filter></activity>

</application><uses-sdkandroid:minSdkVersion="3"/></manifest>

Theapplicationlabelidentifiedby“BirthdayWidget”inthefollowingline

<applicationandroid:icon="@drawable/icon"android:label="BirthdayWidget">

iswhatshowsupinthewidgetpicklist(seeFigure21-2)ofthehomepage.YoucanalsoindicateinthewidgetdefinitionXMLfile(Listing21-2)analternateicontobeshownwhenthewidgetislisted(alsocalledapreview).Theconfigurationactivitydefinitionislikeanyothernormalactivity,exceptthatitneedstodeclareitselfascapableofrespondingtoandroid.appwidget.action.APPWIDGET_CONFIGUREactions.

Refertothewidgetdefinitionfile@xml/bday_appwidget_providerinListing21-2toseehowthewidgetsizeandapathtothelayoutfilearespecified.ThislayoutfileisjustlikeanyotherlayoutfileforaviewinAndroid.Listing21-6showsthelayoutfileweusedtoproducethewidgetlayoutshowninFigure21-4.

Listing21-6.WidgetViewLayoutDefinitionforBDayWidget

<?xmlversion="1.0"encoding="utf-8"?><!--res/layout/bday_widget.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@drawable/box1"><TextViewandroid:id="@+id/bdw_w_name"android:layout_width="fill_parent"android:layout_height="40sp"android:text="Anonymous"android:background="@drawable/box1"android:gravity="center"android:layout_weight="0"/><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_weight="1"><TextViewandroid:id="@+id/bdw_w_days"android:layout_width="wrap_content"android:layout_height="fill_parent"android:gravity="center"android:layout_weight="50"android:text="0"android:textSize="30sp"/><TextViewandroid:id="@+id/bdw_w_button_buy"

android:layout_width="wrap_content"android:layout_height="fill_parent"

android:layout_weight="50"android:gravity="center"android:textSize="20sp"android:text="Buy"android:background="#FF6633"/></LinearLayout><TextViewandroid:id="@+id/bdw_w_date"android:layout_width="fill_parent"android:layout_height="40sp"android:gravity="center"android:layout_weight="0"android:text="1/1/2000"android:background="@drawable/box1"/></LinearLayout>

Someofthecontrolsalsouseashapedefinitionfilecalledbox1.xmltodefinethe

borders.ThecodefortheshapedefinitionfileisshowninListing21-7.

Listing21-7.ABoundaryBoxShapeDefinition

<!--res/drawable/box1.xml--><shapexmlns:android="http://schemas.android.com/apk/res/android"><strokeandroid:width="4dp"android:color="#888888"/><paddingandroid:left="2dp"android:top="2dp"android:right="2dp"android:bottom="2dp"/><cornersandroid:radius="4dp"/></shape>

ImplementingWidgetConfigurationActivityFortheBirthdayWidgetexample,theconfigurationofthewidgetresponsibilitiesareimplementedinConfigureBDayWidgetActivity.SourcecodeforthisclassisinListing21-8.

Listing21-8.ImplementingaConfigurationActivity

//file:ConfigureBDayWidgetActivity.java(ProAndroid5_ch21_TestWidgets.zip)publicclassConfigureBDayWidgetActivityextendsActivity{privatestaticStringtag="ConfigureBDayWidgetActivity";privateintmAppWidgetId=AppWidgetManager.INVALID_APPWIDGET_ID;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.edit_bday_widget);setupButton();//setupthesavebutton

//Getthewidgetinstanceidfromtheintentextra

Intentintent=getIntent();Bundleextras=intent.getExtras();if(extras!=null){mAppWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.bdw_button_update_bday_widget);b.setOnClickListener(

newButton.OnClickListener(){publicvoidonClick(Viewv){saveConfiguration(v);}});}//Readnameanddate.

//CallupdateAppWidgetLocaltosavethevaluesforthisinstance//inthatmethodalsosendtheviewtothehomepage.//ReturntheresultoftheconfigurationactivitytotheSDK//finishtheactivity.privatevoidsaveConfiguration(Viewv){

Stringname=this.getName();Stringdate=this.getDate();if(Utils.validateDate(date)==false){this.setDate("wrongdate:"+date);return;}if(this.mAppWidgetId==AppWidgetManager.INVALID_APPWIDGET_ID){return;}updateAppWidgetLocal(name,date);IntentresultValue=newIntent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,

mAppWidgetId);

setResult(RESULT_OK,resultValue);

finish();}privateStringgetName(){EditTextnameEdit=(EditText)this.findViewById(R.id.bdw_bday_name_id);Stringname=nameEdit.getText().toString();returnname;}privateStringgetDate(){EditTextdateEdit=(EditText)this.findViewById(R.id.bdw_bday_date_id);StringdateString=dateEdit.getText().toString();returndateString;}privatevoidsetDate(StringerrorDate){EditTextdateEdit=(EditText)this.findViewById(R.id.bdw_bday_date_id);dateEdit.setText("error");

dateEdit.requestFocus();}privatevoidupdateAppWidgetLocal(Stringname,Stringdob){

//Createanobjecttoholdthedata:widgetid,name,anddob

BDayWidgetModelm=newBDayWidgetModel(mAppWidgetId,name,dob);//Createtheviewandsendittothehomescreen

updateAppWidget(this,AppWidgetManager.getInstance(this),m);//Usethedatamodelobjecttosavetheid,name,anddobinprefs

m.savePreferences(this);}//AkeymethodwherealotofmagichappenspublicstaticvoidupdateAppWidget(Contextcontext,AppWidgetManagerappWidgetManager,BDayWidgetModelwidgetModel){//ConstructaRemoteViewsObjectfromthewidgetlayoutfileRemoteViewsviews=newRemoteViews(context.getPackageName(),R.layout.bday_widget);

//Usethecontrolidsinthelayouttosetvaluesonthem.

//Noticethatthesemethodsarelimitedandavailableonthe

//ontheRemoteViewsobject.Inotherwordswearenotusingthe

//TextViewdirectlytosetthesevalues.

views.setTextViewText(R.id.bdw_w_name,widgetModel.getName()+":"+widgetModel.iid);

views.setTextViewText(R.id.bdw_w_date,widgetModel.getBday());

//updatethenameviews.setTextViewText(R.id.bdw_w_days,

Long.toString(widgetModel.howManyDays()));

//Setintentstoinvokeotheractivitieswhenwidgetisclickedon

IntentdefineIntent=newIntent(Intent.ACTION_VIEW,Uri.parse("http://www.google.com"));PendingIntentpendingIntent=PendingIntent.getActivity(context,0/*norequestCode*/,defineIntent,0/*noflags*/);views.setOnClickPendingIntent(R.id.bdw_w_button_buy,pendingIntent);

//Tellthewidgetmanagertopainttheremoteview

appWidgetManager.updateAppWidget(widgetModel.iid,views);}}

Beforewecoverwhatthiscodedoes,thelayoutusedbythiswidgetconfigurationactivityisinListing21-9.Thislayoutisstraightforward.YoucanalsoseethisvisuallyinFigure21-3.

Listing21-9.LayoutDefinitionforConfigurationActivity

<?xmlversion="1.0"encoding="utf-8"?><!--res/layout/edit_bday_widget.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/root_layout_id"android:orientation="vertical"

android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:id="@+id/bdw_text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Name:"/><EditTextandroid:id="@+id/bdw_bday_name_id"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Anonymous"/><TextViewandroid:id="@+id/bdw_text2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Birthday(9/1/2001):"/><EditTextandroid:id="@+id/bdw_bday_date_id"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="ex:10/1/2009"/><Buttonandroid:id="@+id/bdw_button_update_bday_widget"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="update"/></LinearLayout>

GoingbacktotheconfigurationactivitycodeinListing21-8,itaccomplishesthefollowingtasks:

ReadingthewidgetinstanceIDfrominvokingintent

Collectingthenameanddateofbirthusingformfields

ObtainingRemoteViewsbyloadingwidgetlayoutfile

SettingtextvaluesontheRemoteViews

RegisteringapendingintentthroughRemoteViews

InvokingtheAppWidgetManagertosendtheRemoteViewstothewidget

SavingthenameanddateofbirthinpreferencesagainstthiswidgetinstanceID.ThisisdonethroughtheclassBDayWidgetModel.Wewilltalkaboutthisshortly.

Returningattheendwitharesult.

NoteThestaticfunctionudpateAppWidgetcanbecalledfromanywhereaslongasyouknowthewidgetID.Thissuggeststhatyoucanupdateawidgetfromanywhereonyourdeviceandfromanyprocess,bothvisualandnonvisual.

NoticehowwearepassingthewidgetIDbacktotheinvokerofthisconfigurationactivity.ThisishowAppWidgetManagerknowsthattheconfigurationactivityiscompletedforthatwidgetinstance.

Let’stalkaboutsavingandretrievalofthewidgetinstancestatethroughBDayWidgetModelobjectinListing21-8.TheroleofBDayWidgetModelobjectistostoreandretrievethreevalues:ThewidgetinstanceID(primarykey),name,anddateofbirth.ThisclassusesthepreferencesAPItopersistandreadbackthesevalues.Alternatively,youcanuseanypersistencemechanismforthisneed.Wearenotincludingthesourcecodeforthisclassasitisquiteasimpleneedtoimplement.Inthedownloadableprojectforthischapterwehaveanimplementationforthisclassthatisabitmoreextensive,wherewecodedareusableframeworktostorevaluesforanyjavaobjectinthepreferences.Wehaveamplydocumentedthesourcecodesothatyoucanuseitasisforotherneedsortweakitfurtherandusereflectiontosimplifyfurther.Intheendyouwillhaveamodelframeworkthatisquiteextensible.Asthisisnottheprimarygoalofthischapterwehavenotgottenintothosedetailshere.Whatmattersforthischapteristhatthesethreevalues,theinstanceID,name,anddobbesavedandretrieved.YoucanfollowthenamesontheBDayWidgetModelasaguide.

ImplementingaWidgetProviderLet’sseenowhowwewillrespondtothelifecycleeventsofwidgetsbyexaminingthewidgetproviderclass.Listing21-10implementsthewidgetproviderclass.

Listing21-10.SourcecodeforSampleWidgetProvider:BDayWidgetProvider

//file:

BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)publicclassBDayWidgetProviderextendsAppWidgetProvider{

privatestaticfinalStringtag="BDayWidgetProvider";publicvoidonUpdate(Contextcontext,AppWidgetManager

appWidgetManager,

int[]appWidgetIds){finalintN=appWidgetIds.length;for(inti=0;i<N;i++){intappWidgetId=appWidgetIds[i];updateAppWidget(context,appWidgetManager,appWidgetId);}}publicvoidonDeleted(Contextcontext,int[]appWidgetIds){

finalintN=appWidgetIds.length;for(inti=0;i<N;i++){BDayWidgetModelbwm=BDayWidgetModel.retrieveModel(context,appWidgetIds[i]);bwm.removePrefs(context);}}publicvoidonEnabled(Contextcontext){

BDayWidgetModel.clearAllPreferences(context);PackageManagerpm=context.getPackageManager();pm.setComponentEnabledSetting(newComponentName("com.androidbook.BDayWidget",".BDayWidgetProvider"),PackageManager.COMPONENT_ENABLED_STATE_ENABLED,PackageManager.DONT_KILL_APP);}

publicvoidonDisabled(Contextcontext){

BDayWidgetModel.clearAllPreferences(context);PackageManagerpm=context.getPackageManager();pm.setComponentEnabledSetting(newComponentName("com.androidbook.BDayWidget",".BDayWidgetProvider"),PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP);}privatevoidupdateAppWidget(Contextcontext,AppWidgetManager

appWidgetManager,

intappWidgetId){BDayWidgetModelbwm=BDayWidgetModel.retrieveModel(context,appWidgetId);if(bwm==null){return;}ConfigureBDayWidgetActivity.updateAppWidget(context,appWidgetManager,bwm);

}}

Inthe“LifeCycleofaWidget”sectionwediscussedtheresponsibilitiesofthesemethods.FortheBirthdayWidget,allthesemethodsmakeuseoftheBDayWidgetModeltoretrievethedataassociatedwithawidgetinstanceforwhichthecallbacksarecalled.SomeofthesemethodsontheBDayWidgetModelareremovePrefs(),retrievePrefs(),andclearAllPreferences().

Theupdatecallbackmethodiscalledforallthewidgetinstancesofthiswidgettype.Thismethodmustupdateallthewidgetinstances.ThewidgetinstancesarepassedinasanarrayofIDs.Foreachid,theonUpdate()methodwilllocatethecorrespondingwidgetinstancemodelandcallthesamemethodthatisusedbytheconfigurationactivity(seeListing21-8)todisplaytheretrievedwidgetmodel.

IntheonDeleted()method,wehaveinstantiatedaBDayWidgetModelandthenaskedittoremoveitselffromthepreferencespersistencestore.

IntheonEnabled()method,becauseitiscalledonlyoncewhenthefirstinstancecomesintoplay,wehaveclearedallpersistenceofthewidgetmodelssothatwestartwithacleanslate.WedothesameintheonDisabled()methodsothatnomemoryofwidgetinstancesexists.

IntheonEnabled()method,weenablethewidgetprovidercomponentsothatitcanreceivebroadcastmessages.IntheonDisabled()method,wedisablethecomponentsothatitwon’tlookforanybroadcastmessages.

Collection-BasedWidgetsStartingwithSDK3.0,Androidhasexpandedthewidgetstoincludewidgetsbasedoncollections.Wedon’thaveroomintheprintcopyofthisbook.Wewillincludethechapterfromthepreviouseditiononcollectionwidgetsatouronlinesitefordownload.

ResourcesHerearehelpfulreferencestothetopicsthatarecoveredinthischapter:

http://developer.android.com/guide/topics/appwidgets/index.htmlOfficialAndroidSDKdocumentationonappwidgets.

http://developer.android.com/reference/android/content/SharedPreferences.htmlSharedPreferencesAPIformanagingstate.

http://developer.android.com/reference/android/content/SharedPreferences.Editor.htmlTheSharedPreferences.EditorAPI,whichisrelatedtosharedpreferences.

http://developer.android.com/guide/practices/ui_guidelines/widget_design.html

Designpleasingwidgetlayouts.

http://developer.android.com/reference/android/widget/RemoteViews.htmlRemoteViewsAPI,usedtopaintandmanipulatewidgetviews.

http://developer.android.com/reference/android/appwidget/AppWidgetManager.htmlWidgetsthemselvesaremanagedbyawidgetmanagerclass.

http://www.androidbook.com/item/3938:Researchnotesusedwhilewritingthischapter,includingasummary,researchlogs,codesnippets,andusefulURLs.

http://www.androidbook.com/free-android-chapters:YoucanusethisURLtodownloadadetailedchapteronlistwidgets.

http://www.androidbook.com/proandroid5/projects:Downloadabletestprojectsforthischapter.ThenameoftheZIPfileforthischapterisProAndroid5_ch21_TestWidgets.zip.

SummaryWidgetsareoftenusedalongsideyourapplicationsinAndroid.Thischapterhascoveredtheessentialsyouneedtocreateandconfigurewidgets.Asupplementalchapteronlistwidgetsisprovidedonline.

Chapter22

TouchScreensManyAndroiddevicesincorporatetouchscreens.Whenadevicedoesnothaveaphysicalkeyboard,muchoftheuserinputmustcomethroughthetouchscreen.Thereforeyourapplicationswilloftenneedtobeabletodealwithtouchinputfromtheuser.You’vemostlikelyalreadyseenthevirtualkeyboardthatdisplaysonthescreenwhentextinputisrequiredfromtheuser.WeusedtouchwithmappingapplicationsinChapter19.Theimplementationsofthetouchscreeninterfacehavebeenhiddenfromyousofar,butnowwe’llshowyouhowtotakeadvantageofthetouchscreen.

Thischapterismadeupofthreemajorparts.ThefirstsectionwilldealwithMotionEventobjects,whichishowAndroidtellsanapplicationthattheuseristouchingatouchscreen.We’llalsocovertheVelocityTracker.Thesecondsectionwilldealwithmultitouch,whereausercanhavemorethanonefingeratatimeonthetouchscreen.Finally,wewillincludeasectionongestures,aspecializedtypeofcapabilityinwhichtouchsequencescanbeinterpretedascommands.

UnderstandingMotionEventsInthissection,we’regoingtocoverhowAndroidtellsapplicationsabouttoucheventsfromtheuser.Fornow,wewillonlybeconcernedwithtouchingthescreenonefingeratatime(we’llcovermultitouchinalatersection).

Atthehardwarelevel,atouchscreenismadeupofspecialmaterialsthatcanpickuppressureandconvertthattoscreencoordinates.Theinformationaboutthetouchisturnedintodata,andthatdataispassedtothesoftwaretodealwithit.

TheMotionEventObjectWhenausertouchesthetouchscreenofanAndroiddevice,aMotionEventobjectiscreated.TheMotionEventcontainsinformationaboutwhereandwhenthetouchtookplace,aswellasotherdetailsofthetouchevent.TheMotionEventobjectgetspassedtoanappropriatemethodinyourapplication.ThiscouldbetheonTouchEvent()methodofaViewobject.RememberthattheViewclassistheparentofquiteafewclassesinAndroid,includingLayouts,Buttons,Lists,Clocks,andmore.ThismeanswecaninteractwithallofthesedifferenttypesofViewobjectsusingtouchevents.Whenthemethodiscalled,itcaninspecttheMotionEventobjecttodecidewhattodo.Forexample,aGoogleMapcouldusetoucheventstomovethemapsidewaystoallowtheusertopanthemaptootherpointsofinterest.Avirtualkeyboardobjectcouldreceivetoucheventstoactivatethevirtualkeystoprovidetextinputtosomeotherpartoftheuserinterface(UI).

ReceivingMotionEventObjectsAMotionEventobjectisoneofasequenceofeventsrelatedtoatouchbytheuser.Thesequencestartswhentheuserfirsttouchesthetouchscreen,continuesthroughanymovementsofthefingeracrossthesurfaceofthetouchscreen,andendswhenthefingerisliftedfromthetouchscreen.Theinitialtouch(anACTION_DOWNaction),themovementssideways(ACTION_MOVEactions),andtheupevent(anACTION_UPaction)ofthefingerallcreateMotionEventobjects.YoucouldreceivequiteafewACTION_MOVEeventsasthefingermovesacrossthesurfacebeforeyoureceivethefinalACTION_UPevent.EachMotionEventobjectcontainsinformationaboutwhatactionisbeingperformed,wherethetouchistakingplace,howmuchpressurewasapplied,howbigthetouchwas,whentheactionoccurred,andwhentheinitialACTION_DOWNoccurred.Thereisafourthpossibleaction,whichisACTION_CANCEL.Thisactionisusedtoindicatethatatouchsequenceisendingwithoutactuallydoinganything.Finally,thereisACTION_OUTSIDE,whichissetinaspecialcasewhereatouchoccursoutsideofourwindowbutwestillgettofindoutaboutit.

Thereisanotherwaytoreceivetouchevents,andthatistoregisteracallbackhandlerfortoucheventsonaViewobject.TheclasstoreceivetheeventsmustimplementtheView.OnTouchListenerinterface,andtheViewobject’ssetOnTouchListener()methodmustbecalledtosetupthehandlerforthatView.TheimplementingclassoftheView.OnTouchListenermustimplementtheonTouch()method.WhereastheonTouchEvent()methodtakesjustaMotionEventobjectasaparameter,onTouch()takesbothaViewandaMotionEventobjectasparameters.ThisisbecausetheOnTouchListenercouldreceiveMotionEventobjectsformultipleviews.Thiswillbecomeclearerwithournextexampleapplication.

IfaMotionEventhandler(eitherthroughtheonTouchEvent()oronTouch()method)consumestheeventandnooneelseneedstoknowaboutit,themethodshouldreturntrue.ThistellsAndroidthattheeventdoesnotneedtobepassedtoanyotherviews.IftheViewobjectisnotinterestedinthiseventoranyfutureeventsrelatedtothistouchsequence,itreturnsfalse.TheonTouchEvent()methodofthebaseclassViewdoesn’tdoanythingandreturnsfalse.SubclassesofViewmayormaynotdothesame.Forexample,aButtonobjectwillconsumeatouchevent,becauseatouchisequivalenttoaclick,andthereforereturnstruefromtheonTouchEvent()method.UponreceivinganACTION_DOWNevent,theButtonwillchangeitscolortoindicatethatitisintheprocessofbeingclicked.TheButtonalsowantstoreceivetheACTION_UPeventtoknowwhentheuserhasletgo,soitcaninitiatethelogicofclickingthebutton.IfaButtonobjectreturnedfalsefromonTouchEvent(),itwouldnotreceiveanymoreMotionEventobjectstotellitwhentheuserliftedafingerfromthetouchscreen.

WhenwewanttoucheventstodosomethingnewwithaparticularViewobject,wecanextendtheclass,overridetheonTouchEvent()method,andputourlogicthere.We

canalsoimplementtheView.OnTouchListenerinterfaceandsetupacallbackhandlerontheViewobject.BysettingupacallbackhandlerwithonTouch(),MotionEventswillbedeliveredtherefirstbeforetheygototheView’sonTouchEvent()method.OnlyiftheonTouch()methodreturnedfalsewouldourView’sonTouchEvent()methodgetcalled.Let’sgettoourexampleapplicationwherethisshouldbeeasiertosee.

NoteWewillgiveyouaURLattheendofthechapterwhichyoucanusetodownloadprojectsofthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.

SettingUpanExampleApplicationListing22-1showstheXMLofalayoutfile.CreateanewAndroidprojectstartingwiththislayout.

Listing22-1.XMLLayoutFileforTouchDemo1

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">

<RelativeLayoutandroid:id="@+id/layout1"android:tag="trueLayoutTop"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1">

<com.androidbook.touch.demo1.TrueButtonandroid:text="ReturnsTrue"android:id="@+id/trueBtn1"android:tag="trueBtnTop"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<com.androidbook.touch.demo1.FalseButtonandroid:text="ReturnsFalse"android:id="@+id/falseBtn1"android:tag="falseBtnTop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/trueBtn1"/>

</RelativeLayout><RelativeLayoutandroid:id="@+id/layout2"

android:tag="falseLayoutBottom"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="#FF00FF">

<com.androidbook.touch.demo1.TrueButtonandroid:text="ReturnsTrue"android:id="@+id/trueBtn2"android:tag="trueBtnBottom"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

<com.androidbook.touch.demo1.FalseButtonandroid:text="ReturnsFalse"android:id="@+id/falseBtn2"android:tag="falseBtnBottom"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/trueBtn2"/>

</RelativeLayout></LinearLayout>

Thereareacoupleofthingstopointoutaboutthislayout.We’veincorporatedtagsonourUIobjects,andwe’llbeabletorefertothesetagsinourcodeaseventsoccuronthem.We’veusedcustomobjects(TrueButtonandFalseButton).You’llseeintheJavacodethattheseareclassesextendedfromtheButtonclass.BecausetheseareButtons,wecanuseallofthesameXMLattributeswewoulduseonotherbuttons.Figure22-1showswhatthislayoutlookslike,andListing22-2showsourbuttonJavacode.

Figure22-1.TheUIofourTouchDemo1application

Listing22-2.JavaCodefortheButtonClassesforTouchDemo1

//ThisfileisBooleanButton.javapublicabstractclassBooleanButtonextendsButton{protectedbooleanmyValue(){returnfalse;}

publicBooleanButton(Contextcontext,AttributeSetattrs){super(context,attrs);}

@OverridepublicbooleanonTouchEvent(MotionEventevent){StringmyTag=this.getTag().toString();Log.v(myTag,"-----------------------------------");Log.v(myTag,MainActivity.describeEvent(this,event));Log.v(myTag,"superonTouchEvent()returns"+

super.onTouchEvent(event));Log.v(myTag,"andI'mreturning"+myValue());return(myValue());}}

//ThisfileisTrueButton.javapublicclassTrueButtonextendsBooleanButton{protectedbooleanmyValue(){returntrue;}

publicTrueButton(Contextcontext,AttributeSetattrs){super(context,attrs);}}

//ThisfileisFalseButton.javapublicclassFalseButtonextendsBooleanButton{

publicFalseButton(Contextcontext,AttributeSetattrs){super(context,attrs);}}

TheBooleanButtonclasswasbuiltsowecanreusetheonTouchEvent()method,whichwe’vecustomizedbyaddingthelogging.Then,wecreatedTrueButtonandFalseButton,whichwillresponddifferentlytotheMotionEventspassedtothem.Thiswillbemadeclearerwhenyoulookatthemainactivitycode,whichisshowninListing22-3.

Listing22-3.JavaCodeforOurMainActivity

//ThisfileisMainActivity.javaimportandroid.view.MotionEvent;importandroid.view.View.OnTouchListener;publicclassMainActivityextendsActivityimplementsOnTouchListener{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

RelativeLayoutlayout1=(RelativeLayout)findViewById(R.id.layout1);layout1.setOnTouchListener(this);ButtontrueBtn1=(Button)findViewById(R.id.trueBtn1);

trueBtn1.setOnTouchListener(this);ButtonfalseBtn1=(Button)findViewById(R.id.falseBtn1);falseBtn1.setOnTouchListener(this);

RelativeLayoutlayout2=(RelativeLayout)findViewById(R.id.layout2);layout2.setOnTouchListener(this);ButtontrueBtn2=(Button)findViewById(R.id.trueBtn2);trueBtn2.setOnTouchListener(this);ButtonfalseBtn2=(Button)findViewById(R.id.falseBtn2);falseBtn2.setOnTouchListener(this);}

@OverridepublicbooleanonTouch(Viewv,MotionEventevent){StringmyTag=v.getTag().toString();Log.v(myTag,"-----------------------------");Log.v(myTag,"Gotview"+myTag+"inonTouch");Log.v(myTag,describeEvent(v,event));if("true".equals(myTag.substring(0,4))){/*Log.v(myTag,"***callingmyonTouchEvent()method***");v.onTouchEvent(event);Log.v(myTag,"***backfromonTouchEvent()method***");*/Log.v(myTag,"andI'mreturningtrue");returntrue;}else{Log.v(myTag,"andI'mreturningfalse");returnfalse;}}

protectedstaticStringdescribeEvent(Viewview,MotionEventevent){StringBuilderresult=newStringBuilder(300);result.append("Action:").append(event.getAction()).append("\n");result.append("Location:").append(event.getX()).append("x").append(event.getY()).append("\n");if(event.getX()<0||event.getX()>view.getWidth()||event.getY()<0||event.getY()>

view.getHeight()){result.append(">>>Touchhaslefttheview<<<\n");}result.append("Edgeflags:").append(event.getEdgeFlags());result.append("\n");result.append("Pressure:").append(event.getPressure());result.append("").append("Size:").append(event.getSize());result.append("\n").append("Downtime:");result.append(event.getDownTime()).append("ms\n");result.append("Eventtime:").append(event.getEventTime());result.append("ms").append("Elapsed:");result.append(event.getEventTime()-event.getDownTime());result.append("ms\n");returnresult.toString();}}

Ourmainactivitycodesetsupcallbacksonourbuttonsandthelayoutssowecanprocessthetouchevents(theMotionEventobjects)foreverythinginourUI.We’veaddedlotsoflogging,soyou’llbeabletotellexactlywhat’sgoingonastoucheventsoccur.OneothergoodideaistoaddthefollowingtagtoyourmanifestfilesoGooglePlayStorewillknowyourapplicationrequiresatouchscreentowork:<uses-configurationandroid:reqTouchScreen=“finger”/>.Forexample,GoogleTVsdon’thavetouchscreens,soitwouldn’tmakesensetotrytorunthisappthere.Whenyoucompileandrunthisapplication,youshouldseeascreenthatlookslikeFigure22-1.

RunningtheExampleApplicationTogetthemostoutofthisapplication,youneedtoopenLogCatinyourIDE(EclipseorAndroidStudio)towatchthemessagesflybyasyoutouchthetouchscreen.Thisworksintheemulatoraswellasonarealdevice.WealsoadviseyoutomaximizetheLogCatwindow,soyoucanmoreeasilyscrollupanddowntoseeallofthegeneratedeventsfromthisapplication.Tomaximizethewindow,justdouble-clicktheLogCattab.Now,gototheapplicationUI,andtouchandreleaseonthetopmostbuttonmarkedReturnsTrue(ifyou’reusingtheemulator,useyourmousetoclickandreleasethebutton).YoushouldseeatleasttwoeventsloggedinLogCat.ThemessagesaretaggedascomingfromtrueBtnTopandwereloggedfromtheonTouch()methodinMainActivity.SeeMainActivity.javafortheonTouch()method’scode.AsyouviewtheLogCatoutput,seewhichmethodcallsareproducingthevalues.Forexample,thevaluedisplayedafterActioncomesfromthegetAction()method.Listing22-4showsasampleof

whatyoumightseeinLogCatfromthesampleapplication.

Listing22-4.SampleLogCatMessagesfromTouchDemo1

trueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:0trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959412msElapsed:0mstrueBtnTopandI'mreturningtruetrueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:2trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959530msElapsed:118mstrueBtnTopandI'mreturningtruetrueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:1trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959567msElapsed:155mstrueBtnTopandI'mreturningtrue

UnderstandingMotionEventContentsThefirsteventhasanactionof0,whichisACTION_DOWN.Thelasteventhasanactionof1,whichisACTION_UP.Ifyouusedarealdevice,youmightseemorethantwoevents.AnyeventsinbetweenACTION_DOWNandACTION_UPwillmostlikelyhaveanactionof2,whichisACTION_MOVE.Theotherpossibilitiesareanactionof3,whichisACTION_CANCEL,or4,whichisACTION_OUTSIDE.Whenusingrealfingersonarealtouchscreen,youcan’talwaystouchandreleasewithoutaslightmovementonthesurface,soexpectsomeACTION_MOVEevents.

NoticetheLocationvalues.ThelocationforaMotionEventhasanxandycomponent,wherexrepresentsthedistancefromtheleft-handsideoftheViewobjecttothepointtouchedandyrepresentsthedistancefromthetopoftheViewobjecttothepointtouched.

Intheemulator,pressureislikely1.0andsizeislikely0.0.Forarealdevice,thepressure

representshowhardthefingerpresseddown,andsizerepresentshowlargethetouchis.Ifyoutouchlightlywiththetipofyourpinkyfinger,thevaluesforpressureandsizewillbesmall.Ifyoupresshardwithyourthumb,bothpressureandsizewillbelarger.Pressinglightlywithyourthumbshouldresultinasmallvalueforpressurebutalargevalueforsize.Thedocumentationsaysthatthevaluesofpressureandsizewillbebetween0and1.However,duetodifferencesinhardware,itmaybeverydifficulttouseanyabsolutenumbersinyourapplicationformakingdecisionsaboutpressureandsize.ItwouldbefinetocomparepressureandsizebetweenMotionEventsastheyoccurinyourapplication,butyoumayrunintotroubleifyoudecidethatpressuremustexceedavaluesuchas0.8tobeconsideredahardpress.Onthatparticulardevice,youmightnevergetavalueabove0.8.Youmightnotevengetavalueabove0.2.

Thedowntimeandeventtimevaluesoperateinthesamewaybetweentheemulatorandarealdevice,theonlydifferencebeingthattherealdevicehasmuchlargervalues.Theelapsedtimesworkthesame.

Theedgeflagsarefordetectingwhenatouchhasreachedtheedgeofthephysicalscreen.TheAndroidSDKdocumentationsaysthattheflagsaresettoindicatethatatouchhasintersectedwithanedgeofthedisplay(top,bottom,left,orright).However,thegetEdgeFlags()methodmayalwaysreturnzero,dependingonwhatdeviceoremulatoritisusedon.Withsomehardware,itistoodifficulttoactuallydetectatouchattheedgeofthedisplay,soAndroidissupposedtopinthelocationtotheedgeandsettheappropriateedgeflagforyou.Thisdoesn’talwayshappen,soyoushouldnotrelyontheedgeflagsbeingsetproperly.TheMotionEventclassprovidesasetEdgeFlags()methodsoyoucansettheflagsyourselfifyouwantto.

ThelastthingtonoticeisthatouronTouch()methodreturnstrue,becauseourTrueButtoniscodedtoreturntrue.ReturningtruetellsAndroidthattheMotionEventobjecthasbeenconsumedandthereisnoreasontogiveittosomeoneelse.ItalsotellsAndroidtokeepsendingtoucheventsfromthistouchsequencetothismethod.That’swhywegottheACTION_UPevent,aswellastheACTION_MOVEeventinthecaseoftherealdevice.

NowtouchtheReturnsFalsebuttonnearthetopofthescreen.Listing22-5showsasampleLogCatoutputforyourReturnsFalsetouch.

Listing22-5.SampleLogCatfromTouchingtheTopReturnsFalseButton

falseBtnTop-----------------------------falseBtnTopGotviewfalseBtnTopinonTouchfalseBtnTopAction:0falseBtnTopLocation:61.309372x44.281494falseBtnTopEdgeflags:0falseBtnTopPressure:0.0627451Size:0.26666668falseBtnTopDowntime:28612178msfalseBtnTopEventtime:28612178msElapsed:0msfalseBtnTopandI'mreturningfalsefalseBtnTop-----------------------------------falseBtnTopAction:0

falseBtnTopLocation:61.309372x44.281494falseBtnTopEdgeflags:0falseBtnTopPressure:0.0627451Size:0.26666668falseBtnTopDowntime:28612178msfalseBtnTopEventtime:28612178msElapsed:0msfalseBtnTopsuperonTouchEvent()returnstruefalseBtnTopandI'mreturningfalsetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:0trueLayoutTopLocation:61.309372x116.281494trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612178msElapsed:0mstrueLayoutTopandI'mreturningtruetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:2trueLayoutTopLocation:61.309372x111.90039trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612217msElapsed:39mstrueLayoutTopandI'mreturningtruetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:1trueLayoutTopLocation:55.08958x115.30792trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612361msElapsed:183mstrueLayoutTopandI'mreturningtrue

Nowyou’reseeingverydifferentbehavior,sowe’llexplainwhathappened.AndroidreceivestheACTION_DOWNeventinaMotionEventobjectandpassesittoouronTouch()methodintheMainActivityclass.OuronTouch()methodrecordstheinformationinLogCatandreturnsfalse.ThistellsAndroidthatouronTouch()methoddidnotconsumetheevent,soAndroidlookstothenextmethodtocall,whichinourcaseistheoverriddenonTouchEvent()methodofourFalseButtonclass.BecauseFalseButtonisanextensionoftheBooleanButtonclass,refertotheonTouchEvent()methodinBooleanButton.javatoseethecode.IntheonTouchEvent()method,weagainwriteinformationtoLogCat,wecalltheparentclass’sonTouchEvent()method,andthenwealsoreturnfalse.NoticethatthelocationinformationinLogCatisexactlythesameasbefore.Thisshouldbeexpected

becausewe’restillinthesameViewobject,theFalseButton.WeseethatourparentclasswantstoreturntruefromonTouchEvent(),andwecanseewhy.IfyoulookatthebuttonintheUI,itshouldbeadifferentcolorfromtheReturnsTruebutton.OurReturnsFalsebuttonnowlookslikeit’spartwaythroughbeingpressed.Thatis,itlookslikeabuttonlookswhenithasbeenpressedbuthasnotbeenreleased.Ourcustommethodreturnedfalseinsteadoftrue.BecauseweagaintoldAndroidthatwedidnotconsumethisevent,byreturningfalse,AndroidneversendstheACTION_UPeventtoourbutton,soourbuttondoesn’tknowthatthefingereverliftedfromthetouchscreen.Therefore,ourbuttonisstillinthepressedstate.Ifwehadreturnedtruelikeourparentwantedto,wewouldeventuallyhavereceivedtheACTION_UPevent,sowecouldchangethecolorbacktothenormalbuttoncolor.Torecap,everytimewereturnfalsefromaUIobjectforareceivedMotionEventobject,AndroidstopssendingMotionEventobjectstothatUIobject,andAndroidkeepslookingforanotherUIobjecttoconsumeourMotionEventobject.

YoumighthaverealizedthatwhenwetouchedourReturnsTruebutton,wedidn’tgetacolorchangeinthebutton.Whyisthat?Well,ouronTouch()methodwascalledbeforeanyactualbuttonmethodsgotcalled,andonTouch()returnedtrue,soAndroidneverbotheredtocalltheReturnsTruebutton’sonTouchEvent()method.Ifyouaddav.onTouchEvent(event);linetotheonTouch()methodjustbeforereturningtrue,youwillseethebuttonchangecolor.YouwillalsoseemoreloglinesinLogCat,becauseouronTouchEvent()methodisalsowritinginformationtoLogCat.

Let’skeepgoingthroughtheLogCatoutput.NowthatAndroidhastriedtwicetofindaconsumerfortheACTION_DOWNeventandfailed,itgoestothenextViewintheapplicationthatcouldpossiblyreceivetheevent,whichinourcaseisthelayoutunderneaththebutton.WecalledourtoplayouttrueLayoutTop,andwecanseethatitreceivedtheACTION_DOWNevent.

NoticethatouronTouch()methodgotcalledagain,althoughnowwiththelayoutviewandnotthebuttonview.EverythingabouttheMotionEventobjectpassedtoonTouch()fortrueLayoutTopisthesameasbefore,includingthetimes,exceptfortheycoordinateofthelocation.Theycoordinatechangedfrom44.281494forthebuttonto116.281494forthelayout.ThismakessensebecausetheReturnsFalsebuttonisnotintheupper-leftcornerofthelayout,it’sbelowtheReturnsTruebutton.Thereforetheycoordinateofthetouchrelativetothelayoutislargerthantheycoordinateofthesametouchrelativetothebutton;thetouchisfurtherawayfromthetopedgeofthelayoutthanitisfromthetopedgeofthebutton.BecauseonTouch()forthetrueLayoutTopreturnstrue,Androidsendstherestofthetoucheventstothelayout,andweseethelogrecordscorrespondingtotheACTION_MOVEandtheACTION_UPevents.GoaheadandtouchthetopReturnsFalsebuttonagain,andnoticethatthesamesetoflogrecordsoccurs.Thatis,onTouch()iscalledforfalseBtnTop,onTouchEvent()iscalledforfalseBtnTop,andthenonTouch()iscalledfortrueLayoutTopfortherestoftheevents.Androidonlystopssendingtheeventstothebuttonforonetouchsequenceatatime.Foranewsequenceoftouchevents,Androidwillsendtothebuttonunlessitgets

anotherreturnoffalsefromthecalledmethod,whichitstilldoesinoursampleapplication.

Nowtouchyourfingeronthetoplayoutbutnotoneitherbutton,andthendragyourfingeraroundabitandliftitoffthetouchscreen(ifyou’reusingtheemulator,justuseyourmousetomakeasimilarmotion).NoticeastreamoflogmessagesinLogCat,wherethefirstrecordhasanactionofACTION_DOWN,andthenmanyACTION_MOVEeventsarefollowedbyanACTION_UPevent.

Now,touchthetopReturnsTruebutton,andbeforeliftingyourfingerfromthebutton,dragyourfingeraroundthescreenandthenliftitoff.Listing22-6showssomenewinformationinLogCat.

Listing22-6.LogCatRecordsShowingaTouchOutsideofOurView

[...logmessagesofanACTION_DOWNeventfollowedbysomeACTION_MOVEevents…]

trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:2trueBtnTopLocation:150.41768x22.628128trueBtnTop>>>Touchhaslefttheview<<<trueBtnTopEdgeflags:0trueBtnTopPressure:0.047058824Size:0.13333334trueBtnTopDowntime:31690859mstrueBtnTopEventtime:31691344msElapsed:485mstrueBtnTopandI'mreturningtrue

[...moreACTION_MOVEeventslogged…]

trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:1trueBtnTopLocation:291.5864x223.43854trueBtnTop>>>Touchhaslefttheview<<<trueBtnTopEdgeflags:0trueBtnTopPressure:0.047058824Size:0.13333334trueBtnTopDowntime:31690859mstrueBtnTopEventtime:31692493msElapsed:1634mstrueBtnTopandI'mreturningtrue

Evenafteryourfingerdragsitselfoffofthebutton,wecontinuetogetnotifiedoftoucheventsrelatedtothebutton.ThefirstrecordinListing22-6showsaneventrecordwherewe’renolongeronthebutton.Inthiscase,thexcoordinateofthetoucheventistotherightoftheedgeofourbuttonobject.However,wekeepgettingcalledwithMotionEventobjectsuntilwegetanACTION_UPevent,becausewecontinuetoreturntruefromtheonTouch()method.Evenwhenyoufinallyliftyourfingeroffofthetouchscreen,andevenifyourfingerisn’tonthebutton,ouronTouch()methodstillgetscalledtogiveustheACTION_UPeventbecausewekeepreturningtrue.Thisis

somethingtokeepinmindwhendealingwithMotionEvents.Whenthefingerhasmovedoffoftheview,wecoulddecidetocancelwhateveroperationmighthavebeenperformedandreturnfalsefromtheonTouch()method,sowedon’tgetnotifiedoffurtherevents.Orwecouldchoosetocontinuetoreceiveevents(byreturningtruefromtheonTouch()method)andonlyperformthelogicifthefingerreturnstoourviewbeforeliftingoff.

ThetouchsequenceofeventsgotassociatedtoourtopReturnsTruebuttonwhenwereturnedtruefromonTouch().ThistoldAndroidthatitcouldstoplookingforanobjecttoreceivetheMotionEventobjectsandjustsendallfutureMotionEventobjectsforthistouchsequencetous.Evenifweencounteranotherviewwhendraggingourfinger,we’restilltiedtotheoriginalviewforthissequence.

ExercisingtheBottomHalfoftheExampleApplicationLet’sseewhathappenswiththelowerhalfofourapplication.GoaheadandtouchtheReturnsTruebuttoninthebottomhalf.WeseethesamethingashappenedwiththetopReturnsTruebutton.BecauseonTouch()returnstrue,Androidsendsustherestoftheeventsinthetouchsequenceuntilthefingerisliftedfromthetouchscreen.Now,touchthebottomReturnsFalsebutton.Onceagain,theonTouch()methodandonTouchEvent()methodsreturnfalse(bothassociatedwiththefalseBtnBottomviewobject).Butthistime,thenextviewtoreceivetheMotionEventobjectisthefalseLayoutBottomobject,anditalsoreturnsfalse.Now,we’refinished.

BecausetheonTouchEvent()methodcalledthesuper’sonTouchEvent()method,thebuttonhaschangedcolortoindicateit’shalfwaythroughbeingpressed.Again,thebuttonwillstaythisway,becausewenevergettheACTION_UPeventinthistouchsequence,becauseourmethodsreturnfalseallthetime.Unlikebefore,eventhelayoutisnotinterestedinthisevent.IfyouweretotouchthebottomReturnsFalsebuttonandholditdownandthendragyourfingeraroundthedisplay,youwouldnotseeanyextrarecordsinLogCat,becausenomoreMotionEventobjectsaresenttous.Wereturnedfalse,soAndroidwon’tbotheruswithanymoreeventsforthistouchsequence.Again,ifwestartanewtouchsequence,wecanseenewLogCatrecordsshowingup.Ifyouinitiateatouchsequenceinthebottomlayoutandnotonabutton,youwillseeasingleeventinLogCatforfalseLayoutBottomthatreturnsfalseandthennothingafterthat(untilyoustartanewtouchsequence).

Sofar,we’veusedbuttonstoshowyoutheeffectsofMotionEventeventsfromtouchscreens.It’sworthpointingoutthat,normally,youwouldimplementlogiconbuttonsusingtheonClick()method.Weusedbuttonsforthissampleapplication,becausethey’reeasytocreateandtheyaresubclassesofViewthatcanthereforereceivetoucheventsjustlikeanyotherview.RememberthatthesetechniquesapplytoanyViewobjectinyourapplication,beitastandardorcustomizedviewclass.

RecyclingMotionEvents

Youmayhavenoticedtherecycle()methodoftheMotionEventclassintheAndroidreferencedocumentation.ItistemptingtowanttorecycletheMotionEventsthatyoureceiveinonTouch()oronTouchEvent(),butdon’tdoit.IfyourcallbackmethodisnotconsumingtheMotionEventobjectandyou’rereturningfalse,theMotionEventobjectislikelytobehandedtosomeothermethodorvieworouractivity,soyoudon’twantAndroidrecyclingityet.Evenifyouconsumedtheeventandreturnedtrue,theeventobjectdoesn’tbelongtoyou,soyoushouldnotrecycleit.

IfyoulookatMotionEventdocumentation,youwillseeafewvariationsofamethodcalledobtain().ThisiseithercreatingacopyofaMotionEventorabrandnewMotionEvent.Yourcopy,oryourbrand-neweventobject,istheeventobjectthatyoushouldrecyclewhenyouaredonewithit.Forexample,ifyouwanttohangontoaneventobjectthatispassedtoyouviaacallback,youshoulduseobtain()tomakeacopy,becauseonceyoureturnfromthecallback,thateventobjectwillberecycledbyAndroid,andyoumaygetstrangeresultsifyoucontinuetouseit.Whenyouarefinishedusingyourcopy,youinvokerecycle()onit.

UsingVelocityTrackerAndroidprovidesaclasstohelphandletouchscreensequences,andthatclassisVelocityTracker.Whenafingerisinmotiononatouchscreen,itmightbenicetoknowhowfastitismovingacrossthesurface.Forexample,iftheuserisdragginganobjectacrossthescreenandletsgo,yourapplicationprobablywantstoshowthatobjectflyingacrossthescreenaccordingly.AndroidprovidesVelocityTrackertohelpwiththemathinvolved.

TouseVelocityTracker,youfirstgetaninstanceofaVelocityTrackerbycallingthestaticmethodVelocityTracker.obtain().YoucanthenaddMotionEventobjectstoitwiththeaddMovement(MotionEventev)method.YouwouldcallthismethodinyourhandlerthatreceivesMotionEventobjects,fromahandlermethodsuchasonTouch(),orfromaview’sonTouchEvent().TheVelocityTrackerusestheMotionEventobjectstofigureoutwhatisgoingonwiththeuser’stouchsequence.OnceVelocityTrackerhasatleasttwoMotionEventobjectsinit,wecanusetheothermethodstofindoutwhat’shappening.

ThetwoVelocityTrackermethods—getXVelocity()andgetYVelocity()—returnthecorrespondingvelocityofthefingerinthexandydirections,respectively.Thevaluereturnedfromthesetwomethodswillrepresentpixelspertimeperiod.Thiscouldbepixelspermillisecondorpersecondorreallyanythingyouwant.TotelltheVelocityTrackerwhattimeperiodtouse,andbeforeyoucancallthesetwogettermethods,youneedtoinvoketheVelocityTracker’scomputeCurrentVelocity(intunits)method.Thevalueofunitsrepresentshowmanymillisecondsareinthetimeperiodformeasuringthevelocity.Ifyouwantpixelspermillisecond,useaunitsvalueof1;ifyouwantpixelspersecond,useaunitsvalueof1000.ThevaluereturnedbythegetXVelocity()and

getYVelocity()methodswillbepositiveifthevelocityistowardtheright(forx)ordown(fory).Thevaluereturnedwillbenegativeifthevelocityistowardtheleft(forx)orup(fory).

WhenyouarefinishedwiththeVelocityTrackerobjectyougotwiththeobtain()method,calltheVelocityTrackerobject’srecycle()method.Listing22-7showsasampleonTouchEvent()handlerforanactivity.ItturnsoutthatanactivityhasanonTouchEvent()callback,whichiscalledwhenevernoviewshavehandledthetouchevent.Becausewe’reusingastock,emptylayout,wehavenoviewsconsumingourtouchevents.

Listing22-7.SampleActivityThatUsesVelocityTracker

importandroid.view.MotionEvent;importandroid.view.VelocityTracker;

publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="VelocityTracker";

/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}

privateVelocityTrackervTracker=null;

publicbooleanonTouchEvent(MotionEventevent){intaction=event.getAction();switch(action){caseMotionEvent.ACTION_DOWN:if(vTracker==null){vTracker=VelocityTracker.obtain();}else{vTracker.clear();}vTracker.addMovement(event);break;caseMotionEvent.ACTION_MOVE:vTracker.addMovement(event);vTracker.computeCurrentVelocity(1000);Log.v(TAG,"Xvelocityis"+vTracker.getXVelocity()+"pixelspersecond");Log.v(TAG,"Yvelocityis"+vTracker.getYVelocity()+

"pixelspersecond");break;caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:Log.v(TAG,"FinalXvelocityis"+vTracker.getXVelocity()+"pixelspersecond");Log.v(TAG,"FinalYvelocityis"+vTracker.getYVelocity()+"pixelspersecond");vTracker.recycle();vTracker=null;break;}returntrue;}}

Obviously,whenyou’veonlyaddedoneMotionEventtoaVelocityTracker(theACTION_DOWNevent),thevelocitiescannotbecomputedasanythingotherthanzero.ButweneedtoaddthestartingpointsothatthesubsequentACTION_MOVEeventscancalculatevelocitiesthen.

VelocityTrackerissomewhatcostlyintermsofperformance,souseitsparingly.Also,makesurethatyourecycleitassoonasyouaredonewithit.TherecanbemorethanoneVelocityTrackerinuseinAndroid,buttheycantakeupalotofmemory,sogiveyoursbackifyou’renotgoingtocontinuetouseit.InListing22-7,wealsousetheclear()methodifwe’restartinganewtouchsequence(thatis,ifwegetanACTION_DOWNeventandourVelocityTrackerobjectalreadyexists)insteadofrecyclingthisoneandobtaininganewone.

MultitouchNowthatyou’veseensingletouchesinaction,let’smoveontomultitouch.MultitouchhasgainedalotofinteresteversincetheTEDconferencein2006atwhichJeffHandemonstratedamultitouchsurfaceforacomputeruserinterface.Usingmultiplefingersonascreenopensupalotofpossibilitiesformanipulatingwhat’sonthescreen.Forexample,puttingtwofingersonanimageandmovingthemapartcouldzoominontheimage.Byplacingmultiplefingersonanimageandturningclockwise,youcouldrotatetheimageonthescreen.ThesearestandardtouchoperationsinGoogleMaps,forinstance.

Ifyouthinkaboutit,though,thereisnomagictothis.Ifthescreenhardwarecandetectmultipletouchesastheyinitiateonthescreen,notifyyourapplicationasthosetouchesmoveintimeacrossthesurfaceofthescreen,andnotifyyouwhenthosetouchesliftoffofthescreen,yourapplicationcanfigureoutwhattheuseristryingtodowiththosetouches.Althoughit’snotmagic,itisn’teasyeither.We’regoingtohelpyouunderstand

multitouchinthissection.

TheBasicsofMultitouchThebasicsofmultitouchareexactlythesameasforsingletouches.MotionEventobjectsgetcreatedfortouches,andtheseMotionEventobjectsarepassedtoyourmethodsjustlikebefore.Yourcodecanreadthedataaboutthetouchesanddecidewhattodo.Atabasiclevel,themethodsofMotionEventarethesame;thatis,wecallgetAction(),getDownTime(),getX(),andsoon.However,whenmorethanonefingeristouchingthescreen,theMotionEventobjectmustincludeinformationfromallfingers,withsomecaveats.TheactionvaluefromgetAction()isforonefinger,notall.Thedowntimevalueisfortheveryfirstfingerdownandmeasuresthetimeaslongasatleastonefingerisdown.ThelocationvaluesgetX()andgetY(),aswellasgetPressure()andgetSize(),cantakeanargumentforthefinger;therefore,youneedtouseapointerindexvaluetorequesttheinformationforthefingeryou’reinterestedin.Therearemethodcallsthatweusedpreviouslythatdidnottakeanyargumenttospecifyafinger(forexample,getX(),getY()),sowhichfingerwouldthevaluesbeforifweusedthosemethods?Youcanfigureitout,butittakessomework.Therefore,ifyoudon’ttakeintoaccountmultiplefingersallofthetime,youmightendupwithsomestrangeresults.Let’sdigintothistofigureoutwhattodo.

ThefirstmethodofMotionEventyouneedtoknowaboutformultitouchisgetPointerCount().ThistellsyouhowmanyfingersarerepresentedintheMotionEventobjectbutdoesn’tnecessarilytellyouhowmanyfingersareactuallytouchingthescreen;thatdependsonthehardwareandontheimplementationofAndroidonthathardware.Youmayfindthat,oncertaindevices,getPointerCount()doesnotreportallfingersthataretouching,justsome.Butlet’spresson.Assoonasyou’vegotmorethanonefingerbeingreportedinMotionEventobjects,youneedtostartdealingwiththepointerindexesandthepointerIDs.

TheMotionEventobjectcontainsinformationforpointersstartingatindex0andgoinguptothenumberoffingersbeingreportedinthatobject.Thepointerindexalwaysstartsat0;ifthreefingersarebeingreported,pointerindexeswillbe0,1,and2.CallstomethodssuchasgetX()mustincludethepointerindexforthefingeryouwantinformationabout.PointerIDsareintegervaluesrepresentingwhichfingerisbeingtracked.PointerIDsstartat0forthefirstfingerdownbutdon’talwaysstartat0oncefingersarecomingandgoingonthescreen.ThinkofapointerIDasthenameofthatfingerwhileitisbeingtrackedbyAndroid.Forexample,imagineapairoftouchsequencesfortwofingers,startingwithfinger1down,andfollowedbyfinger2down,finger1up,andfinger2up.ThefirstfingerdownwillgetpointerID0.ThesecondfingerdownwillgetpointerID1.Oncethefirstfingergoesup,thesecondfingerwillstillbepointerID1.Atthatpoint,thepointerindexforthesecondfingerbecomes0,becausethepointerindexalwaysstartsat0.Inthisexample,thesecondfinger(pointerID1)startsaspointerindex1whenitfirsttouchesdownandthenshiftstopointerindex0oncethefirstfingerleavesthescreen.Evenwhenthesecondfingeristheonlyfingeronthescreen,itremainsaspointerID1.YourapplicationswillusepointerIDstolinktogethertheevents

associatedtoaparticularfingerevenasotherfingersareinvolved.Let’slookatanexample.

Listing22-8showsournewXMLlayoutplusourJavacodeforamultitouchapplication.ThisistheapplicationcalledMultiTouchDemo1.Figure22-2showswhatitshouldlooklike.

Listing22-8.XMLLayoutandJavaforaMultitouchDemonstration

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/androidandroid:id="@+id/layout1"android:tag="trueLayout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1">

<TextViewandroid:text="TouchfingersonthescreenandlookatLogCat"android:id="@+id/message"android:tag="trueText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"/>

</RelativeLayout>

//ThisfileisMainActivity.javaimportandroid.view.MotionEvent;importandroid.view.View.OnTouchListener;

publicclassMainActivityextendsActivityimplementsOnTouchListener{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

RelativeLayoutlayout1=(RelativeLayout)findViewById(R.id.layout1);layout1.setOnTouchListener(this);}

publicbooleanonTouch(Viewv,MotionEventevent){StringmyTag=v.getTag().toString();Log.v(myTag,"-----------------------------");

Log.v(myTag,"Gotview"+myTag+"inonTouch");Log.v(myTag,describeEvent(event));logAction(event);if("true".equals(myTag.substring(0,4))){returntrue;}else{returnfalse;}}

protectedstaticStringdescribeEvent(MotionEventevent){StringBuilderresult=newStringBuilder(500);result.append("Action:").append(event.getAction()).append("\n");intnumPointers=event.getPointerCount();result.append("Numberofpointers:");result.append(numPointers).append("\n");intptrIdx=0;while(ptrIdx<numPointers){intptrId=event.getPointerId(ptrIdx);result.append("PointerIndex:").append(ptrIdx);result.append(",PointerId:").append(ptrId).append("\n");result.append("Location:").append(event.getX(ptrIdx));result.append("x").append(event.getY(ptrIdx)).append("\n");result.append("Pressure:");result.append(event.getPressure(ptrIdx));result.append("Size:").append(event.getSize(ptrIdx));result.append("\n");

ptrIdx++;}result.append("Downtime:").append(event.getDownTime());result.append("ms\n").append("Eventtime:");result.append(event.getEventTime()).append("ms");result.append("Elapsed:");result.append(event.getEventTime()-event.getDownTime());result.append("ms\n");returnresult.toString();

}

privatevoidlogAction(MotionEventevent){intaction=event.getActionMasked();intptrIndex=event.getActionIndex();intptrId=event.getPointerId(ptrIndex);

if(action==5||action==6)action=action-5;

Log.v("Action","Pointerindex:"+ptrIndex);Log.v("Action","PointerId:"+ptrId);Log.v("Action","Trueactionvalue:"+action);}}

Figure22-2.Ourmultitouchdemonstrationapplication

Ifyouonlyhavetheemulator,thisapplicationwillstillwork,butyouwon’tbeabletogetmultiplefingerssimultaneouslyonthescreen.You’llseeoutputsimilartowhatwesawin

thepreviousapplication.Listing22-9showssampleLogCatmessagesforatouchsequencelikewedescribedearlier.Thatis,thefirstfingerpressesonthescreen,andthenthesecondfingerpresses,thefirstfingerleavesthescreen,andthesecondfingerleavesthescreen.Listing22-9.SampleLogCatOutputforaMultitouchApplication

trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:0trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.047058824Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733650msElapsed:0msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:0trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733740msElapsed:90msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:261trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733962msElapsed:312msActionPointerindex:1ActionPointerId:1ActionTrueActionvalue:0trueLayout-----------------------------

trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:111.474594x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734189msElapsed:539msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:6trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:111.474594x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734228msElapsed:578msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:1trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:1trueLayoutLocation:318.84656x191.45105trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734240msElapsed:590msActionPointerindex:0ActionPointerId:1ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:1trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:1

trueLayoutLocation:314.95224x190.5625trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734549msElapsed:899msActionPointerindex:0ActionPointerId:1ActionTrueActionvalue:1

UnderstandingMultitouchContentsWe’llnowdiscusswhatisgoingonwiththisapplication.ThefirsteventweseeistheACTION_DOWN(actionvalueof0)ofthefirstfinger.WelearnaboutthisusingthegetAction()method.PleaserefertothedescribeEvent()methodinMainActivity.javatofollowalongwithwhichmethodsproducewhichoutput.Wegetonepointerwithindex0andpointerID0.Afterthat,you’llprobablyseeseveralACTION_MOVEevents(actionvalueof2)forthisfirstfinger,eventhoughwe’reonlyshowingoneoftheseinListing22-9.WestillonlyhaveonepointerandtheindexandIDarestillboth0.

Alittlelaterwegetthesecondfingertouchingthescreen.Theactionisnowadecimalvalueof261.Whatdoesthismean?Theactionvalueisactuallymadeupoftwoparts:anindicatorofwhichpointertheactionisforandwhatactionthatpointerisdoing.Convertingdecimal261tohexadecimal,weget0x00000105.Theactionisthesmallestbyte(5inthiscase),andthepointerindexisthenextbyteover(1inthiscase).NotethatthistellsusthepointerindexbutnotthepointerID.Ifyoupressedathirdfingerontothescreen,theactionwouldbe0x00000205(ordecimal517).Afourthfingerwouldbe0x00000305(ordecimal773)andsoon.Youhaven’tseenanactionvalueof5yet,butit’sknownasACTION_POINTER_DOWN.It’sjustlikeACTION_DOWNexceptthatit’susedinmultitouchsituations.

Now,lookatthenextpairofrecordsfromLogCatinListing22-9.ThefirstrecordisforanACTION_MOVEevent(actionvalueof2).Rememberthatitisdifficulttokeepfingersfrommovingonarealscreen.We’reonlyshowingoneACTION_MOVEevent,butyoumightseeseveralwhenyoutrythisforyourself.Whenthefirstfingerisliftedoffofthescreen,wegetanactionvalueof0x00000006(ordecimal6).Likebefore,wehavepointerindex0andanactionvaluethatisACTION_POINTER_UP(similartoACTION_UPbutformultitouchsituations).Ifthesecondfingerwasliftedinamultitouchsituation,wewouldgetanactionvalueof0x00000106(ordecimal262).NoticehowwestillhaveinformationfortwofingerswhenwegettheACTION_UPforoneofthem.

ThelastpairofrecordsinListing22-9showsonemoreACTION_MOVEeventforthesecondfinger,followedbyanACTION_UPforthesecondfinger.Thistime,weseeanactionvalueof1(ACTION_UP).Wedidn’tgetanactionvalueof262,butwe’llexplainthatnext.Also,noticethatoncethefirstfingerleftthescreen,thepointerindexforthesecondfingerhaschangedfrom1to0,butthepointerIDhasremainedas1.

ACTION_MOVEeventsdonottellyouwhichfingermoved.Youwillalwaysgetanaction

valueof2foramoveregardlessofhowmanyfingersaredownorwhichfingerisdoingthemoving.AlldownfingerpositionsareavailablewithintheMotionEventobject,soyouneedtoreadthepositionsandthenfigurethingsout.Ifthere’sonlyonefingerleftonthescreen,thepointerIDwilltellyouwhichfingeritisthat’sstillmovingbecauseit’stheonlyfingerleft.InListing22-9,whenthesecondfingerwastheonlyoneleftonthescreen,theACTION_MOVEeventhadapointerindexof0andapointerIDof1,soweknewitwasthesecondfingerthatwasmoving.

NotonlycanaMotionEventobjectcontainmoveeventsformorethanonefinger,butitcanalsocontainmultiplemoveeventsperfinger.Itdoesthisusinghistoricalvaluescontainedwithintheobject.AndroidshouldreportallhistorysincethelastMotionEventobject.SeegetHistoricalSize()andtheothergetHistorical…()methods.

GoingbacktothebeginningofListing22-9,thefirstfingerdownispointerindex0andpointerID0,sowhydon’tweget0x00000005(ordecimal5)fortheactionvaluewhenthefirstfingerispressedtothescreenbeforeanyotherfingers?Unfortunately,thisquestiondoesn’thaveahappyanswer.Wecangetanactionvalueof5inthefollowingscenario:pressthefirstfingertothescreenandthenthesecondfinger,resultinginactionvaluesof0and261(ignoringtheACTION_MOVEeventsforthemoment).Now,liftthefirstfinger(actionvalueof6),andpressitbackdownonthescreen.ThepointerIDofthesecondfingerremainedas1.Forthemomentwhenthefirstfingerwasintheair,ourapplicationknewaboutpointerID1only.Oncethefirstfingertouchedthescreenagain,AndroidreassignedpointerID0tothefirstfingerandgaveitpointerindex0aswell.Becausenowweknowtherearemultiplefingersinvolved,wegetanactionvalueof5(pointerindexof0andtheactionvalueof5).Theanswertothequestion,therefore,isbackwardcompatibility,butitisnotahappyanswer.Theactionvaluesof0and1arepre-multitouch.

Whenonlyonefingerremainsonthescreen,Androidtreatsitlikeasingle-touchcase.SowegettheoldACTION_UPvalueof1insteadofamultitouchACTION_UPvalueof6.Ourcodewillneedtoconsiderthesecasescarefully.Apointerindexof0couldresultinanACTION_DOWNvalueof0or5,dependingonwhichpointersareinplay.ThelastfingerupwillgetanACTION_UPvalueof1nomatterwhichpointerIDithas.

Thereisanotheractionwehaven’tmentionedsofar:ACTION_SCROLL(valueof8),introducedinAndroid3.1.Thiscomesfromaninputdevicelikeamouse,notatouchscreen.Infact,asyoucanseefromthemethodsinMotionEvent,theseobjectscanbeusedforlotsofthingsotherthantouchscreentouches.Wewon’tbecoveringtheseotherinputdevicesinthisbook.

GesturesGesturesareaspecialtypeofatouchscreenevent.ThetermgestureisusedforavarietyofthingsinAndroid,fromasimpletouchsequencelikeaflingorapinchtotheformalGestureclass.Flings,pinches,longpresses,andscrollshaveexpectedbehaviorswith

expectedtriggers.Thatis,itisprettycleartomostpeoplethataflingisagesturewhereafingertouchesthescreen,dragssomewhatquicklyoffinasingledirection,andthenliftsup.Forexample,whensomeoneusesaflingintheGalleryapplication(theonethatshowsimagesinaleft-to-rightchain),theimageswillmovesidewaystoshownewimagestotheuser.

Inthefollowingsections,youwilllearnhowtoimplementapinchgesture,fromwhichyoucaneasilyimplementtheothercommongestures.TheformalGestureclassreferstogesturesdrawnbyauseronatouchscreen,sothatanapplicationcanreacttothosegestures.Thetypicalexampleincludesdrawinglettersofthealphabetwhichtheapplicationcanunderstandasletters.TheformalGestureclassisnotcoveredinthisbook.Let'slearntopinch!

ThePinchGestureOneofthecoolapplicationsofmultitouchisthepinchgesture,whichisusedforzooming.Theideaisthatifyouplacetwofingersonthescreenandspreadthemapart,theapplicationshouldrespondbyzoomingin.Ifyourfingerscometogether,theapplicationshouldzoomout.Theapplicationisusuallyshowingimages,whichcouldbemaps.

Beforewegettothepinchgesture’snativesupport,wefirstneedtocoveraclassthat’sbeenaroundfromthebeginning—GestureDetector.

GestureDetectorandOnGestureListenersThefirstclasstohelpuswithgesturesisGestureDetector,whichhasbeenaroundfromtheverybeginningofAndroid.ItspurposeinlifeistoreceiveMotionEventobjectsandtelluswhenasequenceofeventslookslikeacommongesture.WepassallofoureventobjectstotheGestureDetectorfromourcallback,anditcallsothercallbackswhenitrecognizesagesture,suchasaflingorlongpress.WeneedtoregisteralistenerforthecallbacksfromtheGestureDetector,andthisiswhereweputourlogicthatsayswhattodoiftheuserhasperformedoneofthesecommongestures.Unfortunately,thisclassdoesnottellusifapinchgestureistakingplace;forthat,weneedtouseanewclass,whichwe’llgettoshortly.

Thereareafewwaystobuildthelistenerside.Yourfirstoptionistowriteanewclassthatimplementstheappropriategesturelistenerinterface:forexample,theGestureDetector.OnGestureListenerinterface.Thereareseveralabstractmethodsthatmustbeimplementedforeachofthepossiblecallbacks.

Yoursecondoptionistopickoneofthesimpleimplementationsofalistenerandoverridetheappropriatecallbackmethodsthatyoucareabout.Forexample,theGestureDetector.SimpleOnGestureListenerclasshasimplementedalloftheabstractmethodstodonothingandreturnfalse.Allyouhavetodoisextendthatclassandoverridethefewmethodsyouneedtoactonthosefewgesturesyoucareabout.Theothermethodshavetheirdefaultimplementations.It’smorefuture-prooftochoosethesecondoptionevenifyoudecidetooverrideallofthecallbackmethods,becauseifa

futureversionofAndroidaddsanotherabstractcallbackmethodtotheinterface,thesimpleimplementationwillprovideadefaultcallbackmethod,soyou’recovered.

We’regoingtoexploreScaleGestureDetector,plusthecorrespondinglistenerclass,toseehowtousethepinchgesturetoresizeanimage.Inthisexample,weextendthesimpleimplementation(ScaleGestureDetector.SimpleOnScaleGestureListener)forourlistener.Listing22-10hastheXMLlayoutandtheJavacodeforourMainActivity.

Listing22-10.LayoutandJavaCodeforthePinchGestureUsingScaleGestureDetector

<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/layout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">

<TextViewandroid:text="Usethepinchgesturetochangetheimagesize"android:layout_width="match_parent"android:layout_height="wrap_content"/>

<ImageViewandroid:id="@+id/image"android:src="@drawable/icon"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="matrix"/>

</LinearLayout>

//ThisfileisMainActivity.javapublicclassMainActivityextendsActivity{privatestaticfinalStringTAG="ScaleDetector";privateImageViewimage;privateScaleGestureDetectormScaleDetector;privatefloatmScaleFactor=1f;privateMatrixmMatrix=newMatrix();@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

image=(ImageView)findViewById(R.id.image);mScaleDetector=newScaleGestureDetector(this,newScaleListener());}

@OverridepublicbooleanonTouchEvent(MotionEventev){Log.v(TAG,"inonTouchEvent");//GivealleventstoScaleGestureDetectormScaleDetector.onTouchEvent(ev);

returntrue;}

privateclassScaleListenerextendsScaleGestureDetector.SimpleOnScaleGestureListener{@OverridepublicbooleanonScale(ScaleGestureDetectordetector){mScaleFactor*=detector.getScaleFactor();

//Makesurewedon'tgettoosmallortoobigmScaleFactor=Math.max(0.1f,Math.min(mScaleFactor,5.0f));

Log.v(TAG,"inonScale,scalefactor="+mScaleFactor);mMatrix.setScale(mScaleFactor,mScaleFactor);

image.setImageMatrix(mMatrix);image.invalidate();returntrue;}}}

Ourlayoutisstraightforward.WehaveasimpleTextViewwithourmessagetousethepinchgesture,andwehaveourImageViewwiththestandardAndroidicon.We’regoingtoresizethisiconimageusingapinchgesture.Ofcourse,feelfreetosubstituteyourownimagefileinsteadoftheicon.Justcopyyourimagefileintoadrawablefolder,andbesuretochangetheandroid:srcattributeinthelayoutfile.Noticetheandroid:scaleTypeattributeintheXMLlayoutforourimage.ThistellsAndroidthatwe’llbeusingagraphicsmatrixtodoscalingoperationsontheimage.Althoughagraphicsmatrixcanalsodomovementofourimagewithinthelayout,we’reonlygoingtofocusonscalingfornow.AlsonoticethatwesettheImageViewsizetoasbigaspossible.Aswescaletheimage,wedon’twantitclippedbytheboundariesoftheImageView.

Thecodeisalsostraightforward.WithinonCreate(),wegetareferencetoourimageandcreateourScaleGestureDetector.WithinouronTouchEvent()callback,

allwedoispasseveryeventobjectwegettotheScaleGestureDetector’sonTouchEvent()methodandreturntruesowekeepgettingnewevents.ThisallowstheScaleGestureDetectortoseealleventsanddecidewhentonotifyusofgestures.

TheScaleListeneriswherethezoominghappens.Thereareactuallythreecallbackswithinthelistenerclass:onScaleBegin(),onScale(),andonScaleEnd().Wedon’tneedtodoanythingspecialwiththebeginandendmethods,sowedidn’timplementthemhere.

WithinonScale(),thedetectorpassedincanbeusedtofindoutlotsofinformationaboutthescalingoperation.Thescalefactorisavaluethathoversaround1.Thatis,asthefingerspinchclosertogether,thisvalueisslightlybelow1;asthefingersmoveapart,thisvalueisslightlylargerthan1.OurmScaleFactormemberstartsat1,soitgetsprogressivelysmallerorlargerthan1asthefingersmovetogetherorapart.IfmScaleFactorequals1,ourimagewillbenormalsize.Otherwise,ourimagewillbesmallerorlargerthannormalasmScaleFactormovesbeloworabove1.WesetsomeboundsonmScaleFactorwiththeelegantmin/maxfunctioncombination.Thispreventsourimagefromgettingtoosmallortoolarge.WethenusemScaleFactortoscalethegraphicsmatrix,andweapplythenewlyscaledmatrixtoourimage.Theinvalidate()callforcesaredrawoftheimageonthescreen.

ToworkwiththeOnGestureListenerinterface,you’ddosomethingverysimilartowhatwe’vedoneherewithourScaleListener,exceptthatthecallbackswillbefordifferentcommongesturessuchassingletap,doubletap,longpress,andfling.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther.

www.androidbook.com/proandroid5/projects:Downloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch22_Touchscreens.zip.Thiszipfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneofthesezipfiles.

www.ted.com/talks/jeff_han_demos_his_breakthrough_touchscreen.htmlJeffHandemonstrateshismultitouchcomputeruserinterfaceatTEDin2006—verycool.

http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html:AnAndroidblogpostaboutmultitouchoffersyetanotherwaytoimplementaGestureDetectorinside

anextensionofaview.

SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouttouchscreenssofar:

MotionEventasthefoundationonwhichtouchhandlingisdone

DifferentcallbacksthathandletoucheventsonaViewobjectandthroughanOnTouchListener

Differenttypesofeventsthatoccurduringatouchsequence

Howtoucheventstravelthroughanentireviewhierarchy,unlesshandledalongtheway

InformationthataMotionEventobjectcontainsabouttouches,includingformultiplefingers

WhentorecycleaMotionEventobjectandwhennotto

Determiningthespeedatwhichafingerdragsacrossascreen

Thewonderfulworldofmultitouch,andtheinternaldetailsofhowitworks

Implementingthepinchgesture,aswellasothercommongestures

Chapter23

ImplementingDragandDropInthelastchapter,wecoveredtouchscreens,theMotionEventclass,andgestures.Youlearnedhowtousetouchtomakethingshappeninyourapplication.Oneareathatwedidn’tcoverwasdraganddrop.Onthesurface,draganddropseemslikeitshouldbefairlysimple:touchanobjectonthescreen,dragitacrossthescreen(usuallyoversomeotherobject),andletgo,andtheapplicationshouldtaketheappropriateaction.Inmanycomputeroperatingsystems,thisisacommonwaytodeleteafilefromthedesktop;youjustdragthefile’sicontothetrash-binicon,andthefilegetsdeleted.InAndroid,youmayhaveseenhowtorearrangeiconsonthehomescreenbydraggingthemtonewlocationsortothetrash.

Thischapterisgoingtogoindepthintodraganddrop.PriortoAndroid3.0,developerswereontheirownwhenitcametodraganddrop.ButbecausetherearestillquiteafewphonesoutthererunningAndroid2.3,we’llshowyouhowtododraganddroponthem.We’llshowyoutheoldwayinthefirstsectionofthischapter,andthenwe'llshowyouthenewwayinthesecondpart.

ExploringDragandDropInthisnextexampleapplication,we’regoingtotakeawhitedotanddragittoanewlocationinouruserinterface.We’realsogoingtoplacethreecountersinouruserinterface,andiftheuserdragsthewhitedottooneofthecounters,thatcounterwillincrementandthedotwillreturnbacktoitsstartingplace.Ifthedotisdraggedsomewhereelseonthescreen,we’lljustleaveitthere.

NoteSeethe“References”sectionattheendofthischapterfortheURLfromwhichyoucanimporttheseprojectsintoyourIDEdirectly.We’llonlyshowcodeinthetexttoexplainconcepts.You'llneedtodownloadthecodetocreateaworkingexampleapplication.

ThefirstsampleapplicationforthischapteriscalledTouchDragDemo.Therearetwokeyfileswewanttotalkaboutinthissection:

/res/layout/main.xml

/src/com/androidbook/touch/dragdemo/Dot.java

Themain.xmlfilecontainsourlayoutforthedrag-and-dropdemo.ItisshowninListing23-1.SomeofthekeyconceptswewantyoutonoticearetheuseofaFrameLayoutasthetop-levellayout,insideofwhichisaLinearLayoutcontainingTextViewsandacustomViewclasscalledDot.BecausetheLinearLayoutand

DotcoexistwithintheFrameLayout,theirpositionsandsizesdon’treallyimpacteachother,buttheywillbesharingthescreenrealestate,oneontopoftheother.TheUIforthisapplicationisshowninFigure23-1.

Listing23-1.ExampleLayoutXMLforOurDragExample

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#0000ff">

<LinearLayoutandroid:id="@+id/counters"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">

<TextViewandroid:id="@+id/top"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:layout_marginTop="30dp"android:layout_marginBottom="30dp"android:padding="10dp"/>

<TextViewandroid:id="@+id/middle"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:layout_marginBottom="30dp"android:padding="10dp"/>

<TextViewandroid:id="@+id/bottom"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:padding="10dp"/></LinearLayout>

<com.androidbook.touch.dragdemo.Dotandroid:id="@+id/dot"android:layout_width="match_parent"android:layout_height="match_parent"/>

</FrameLayout>

Figure23-1.UserinterfaceforTouchDragDemo

NotethatthepackagenameinthelayoutXMLfilefortheDotelementmustmatchthepackagenameyouuseforyourapplication.Asmentioned,thelayoutofDotisseparatedfromtheLinearLayout.Thisisbecausewewantthefreedomtomovethedotaroundthescreen,whichiswhywechosethelayout_widthandlayout_heightof“match_parent”.Whenwedrawthedotonthescreen,wewantittobevisible,andifweconstrictthesizeofourdot’sviewtothediameterofthedot,wewon’tbeabletoseeitwhenwedragitawayfromourstartingplace.

NoteTechnically,wecouldsetandroid:clipChildrentotrueintheFrameLayouttagandsetthelayoutwidthandheightofthedottowrap_content,butthatdoesn’tfeelasclean.

Foreachofthecounters,wesimplylaythemoutwithabackground,padding,margins,andgravitytogetthemtoshowupalongtheright-handsideofthescreen.Westartthemoffatzero,butasyou’llsoonsee,we'llbeincrementingthosevaluesasdotsaredraggedovertothem.AlthoughwechosetouseTextViewsinthisexample,youcouldusejustaboutanyViewobjectasadroptarget.NowwewilllookattheJavacodeforourDotclassinListing23-2.

Listing23-2.JavaCodeforOurDotClass

publicclassDotextendsView{privatestaticfinalStringTAG="TouchDrag";privatefloatleft=0;privatefloattop=0;privatefloatradius=20;privatefloatoffsetX;privatefloatoffsetY;

privatePaintmyPaint;privateContextmyContext;

publicDot(Contextcontext,AttributeSetattrs){super(context,attrs);

//Savethecontext(theactivity)myContext=context;

myPaint=newPaint();myPaint.setColor(Color.WHITE);myPaint.setAntiAlias(true);}

publicbooleanonTouchEvent(MotionEventevent){intaction=event.getAction();floateventX=event.getX();floateventY=event.getY();switch(action){caseMotionEvent.ACTION_DOWN://Firstmakesurethetouchisonourdot,//sincethesizeofthedot'sviewis//technicallythewholelayout.Ifthe//touchis*not*within,thenreturnfalse//indicatingwedon'twantanymoreevents.if(!(left-20<eventX&&eventX<left+radius*2+20&&top-20<eventY&&eventY<top+radius*2+20))returnfalse;

//Remembertheoffsetofthetouchascompared//toourleftandtopedges.offsetX=eventX-left;offsetY=eventY-top;break;caseMotionEvent.ACTION_MOVE:caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:left=eventX-offsetX;top=eventY-offsetY;if(action==MotionEvent.ACTION_UP){checkDrop(eventX,eventY);}break;}invalidate();returntrue;

}

privatevoidcheckDrop(floatx,floaty){//Seeifthex,yofourdroplocationisnearto//oneofourcounters.Ifso,incrementit,and//resetthedotbacktoitsstartingpositionLog.v(TAG,"checkingdroptargetfor"+x+","+y);

intviewCount=((MainActivity)myContext).counterLayout.getChildCount();

for(inti=0;i<viewCount;i++){Viewview=((MainActivity)myContext).counterLayout.getChildAt(i);if(view.getClass()==TextView.class){Log.v(TAG,"Isthedroptotherightof"+(view.getLeft()-20));Log.v(TAG,"andverticallybetween"+(view.getTop()-20)+"and"+(view.getBottom()+20)+"?");if(x>view.getLeft()-20&&view.getTop()-20<y&&y<view.getBottom()+20){Log.v(TAG,"Yes.Yesitis.");

//IncreasethecountvalueintheTextViewbyoneintcount=Integer.parseInt(((TextView)view).getText().toString());((TextView)view).setText(String.valueOf(++count));

//Resetthedotbacktostartingpositionleft=top=0;break;}}}}

publicvoiddraw(Canvascanvas){canvas.drawCircle(left+radius,top+radius,radius,myPaint);

}}

Whenyourunthisapplication,youwillseeawhitedotonabluebackground.Youcantouchthedotanddragitaroundthescreen.Whenyouliftyourfinger,thedotstayswhereitisuntilyoutouchitagainanddragitsomewhereelse.Thedraw()methodputsthedotatitscurrentlocationofleftandtop,adjustedbythedot’sradius.ByreceivingMotionEventobjectsintheonTouchEvent()method,wecanmodifytheleftandtopvaluesbythemovementofourtouch.

Becausetheuserwon’talwaystouchtheexactcenteroftheobject,thetouchcoordinateswillnotbethesameasthelocationcoordinatesoftheobject.Thatisthepurposeoftheoffsetvalues:togetusbacktotheleftandtopedgesofourdotfromthepositionofthetouch.Butevenbeforewestartadragoperation,wewanttobesurethattheuser’stouchisconsideredcloseenoughtothedottobevalid.Iftheusertouchesthescreenfarawayfromthedot,whichistechnicallywithintheviewlayoutofthedot,wedon’twantthattostartadragsequence.Thatiswhywelooktoseeifthetouchiswithinthewhitedotitself;ifitisnot,wesimplyreturnfalse,whichpreventsreceivinganymoretoucheventsinthattouchsequence.

Whenyourfingerstartsmovingacrossthescreen,weadjustthelocationoftheobjectbythedeltasinxandybasedontheMotionEventsthatweget.Whenyoustopmoving(ACTION_UP),wefinalizeourlocationusingthelastcoordinatesofyourtouch.Wedon’thavetoworryaboutscrollbarsinthisexample,whichcouldcomplicatethecalculationoftheobject’spositionofourobjectonthescreen.Butthebasicprincipleisstillthesame.ByknowingthestartinglocationoftheobjecttobemovedandkeepingtrackofthedeltavaluesofatouchfromACTION_DOWNthroughtoACTION_UP,wecanadjustthelocationoftheobjectonthescreen.

Droppinganobjectontoanotherobjectonthescreenhasmuchlesstodowithtouchthanitdoeswithknowingwherethingsareonthescreen.Aswedraganobjectaroundthescreen,weareawareofitspositionrelativetooneormorereferencepoints.Wecanalsointerrogateobjectsonthescreenfortheirlocationsandsizes.Wecanthendetermineifourdraggedobjectis“over”anotherobject.Thetypicalprocessoffiguringoutadroptargetforadraggedobjectistoiteratethroughtheavailableobjectsthatcanbedroppedonanddetermineifourcurrentpositionoverlapswiththatobject.Eachobject’ssizeandposition(andsometimesshape)canbeusedtomakethisdetermination.IfwegetanACTION_UPevent,meaningthattheuserhasletgoofourdraggedobject,andtheobjectisoversomethingwecandroponto,wecanfirethelogictoprocessthedropaction.

Weusedthisapproachinoursampleapplication.WhentheACTION_UPactionisdetected,wethenlookthroughthechildviewsoftheLinearLayout,andforeachTextViewthatisfound,wecomparethelocationofthetouchtotheedgesoftheTextView(plusalittlebitextra).IfthetouchiswithinthatTextView,wegrabthecurrentnumericvalueoftheTextView,incrementitbyone,andwriteitback.Ifthishappens,thepositionofthedotisresetbacktoitsstartingplace(left=0,top=0)forthenextdrag.

OurexampleshowsyouthebasicsofawaytododraganddropinAndroidpriorto3.0.Withthisyoucouldimplementdrag-and-dropfeaturesinyourapplication.Thismightbetheactionofdraggingsomethingtothetrashcan,wheretheobjectbeingdraggedshouldbedeleted,oritcouldbedraggingafiletoafolderforthepurposesofmovingorcopyingit.Toembellishyourapplication,youcouldpre-identifywhichviewsarepotentialdroptargetsandcausethemtovisuallychangeasadragstarts.Ifyouwantedthedraggedobjecttodisappearfromthescreenwhenitisdropped,youcouldalwaysprogrammaticallyremoveitfromthelayout(seethevariousremoveViewmethodsinViewGroup).

Nowthatyou’veseenthehardwaytododraganddrop,we’dliketoshowyouthedrag-and-dropsupportthatwasaddedinAndroid3.0.

BasicsofDragandDropin3.0+PriortoAndroid3.0,therewasnodirectsupportfordraganddrop.YoulearnedinthefirstsectionofthischapterhowtodragaViewaroundthescreen;youalsolearnedthatitwaspossibletousethecurrentlocationofthedraggedobjecttodetermineiftherewasadroptargetunderneath.WhentheMotionEventforthefinger-upeventwasreceived,yourcodecouldfigureoutifthatmeantadrophadoccurred.Althoughthiswasdoable,itcertainlywasn’taseasyashavingdirectsupportinAndroidforthedrag-and-dropoperation.Younowhavethatdirectsupport.

Atitsmostbasic,thedrag-and-dropoperationstartswithaviewdeclaringthatadraghasstarted;thenallinterestedpartieswatchthedragtakeplaceuntilthedropeventisfired.Ifaviewcatchesthedropeventandwantstoreceiveit,thenadraganddrophasjustoccurred.Ifthereisnoviewtoreceivethedrop,oriftheviewthatreceivesitdoesn’twantit,thennodroptakesplace.DraggingiscommunicatedthroughtheuseofaDragEventobject,whichispassedtoallofthedraglistenersavailable.

WithintheDragEventobjectaredescriptorsforlotsofinformation,dependingontheinitiatorofthedragsequence.Forexample,theDragEventcancontainobjectreferencestotheinitiatoritself,stateinformation,textualdata,URIs,orprettymuchwhateveryouwanttopassthroughthedragsequence.

Informationcouldbepassedthatresultsinview-to-viewdynamiccommunication;however,theoriginatordatainaDragEventobjectissetwhentheDragEventiscreated,anditstaysthesamethereafter.Inadditiontothisdata,theDragEventhasanactionvalueindicatingwhatisgoingonwiththedragsequence,andlocationinformationindicatingwherethedragisonthescreen.

ADragEventhassixpossibleactions:

ACTION_DRAG_STARTEDindicatesthatanewdragsequencehasbegun.

ACTION_DRAG_ENTEREDindicatesthatthedraggedobjecthasbeendraggedintotheboundariesofaspecificview.

ACTION_DRAG_LOCATIONindicatesthatthedraggedobjecthasbeendraggedonthescreentoanewlocation.

ACTION_DRAG_EXITEDindicatesthatthedraggedobjecthasbeendraggedoutsidetheboundariesofaspecificview.

ACTION_DROPindicatesthattheuserhasletgoofthedraggedobject.Itisuptothereceiverofthiseventtodeterminewhetherthistrulymeansadrophasoccurred.

ACTION_DRAG_ENDEDtellsalldraglistenersthatthepreviousdragsequencehasended.TheDragEvent.getResult()methodindicatesasuccessfuldroporfailure.

Youmightthinkthatyouneedtosetupadraglisteneroneachviewinthesystemthatcouldparticipateinadragsequence;but,infact,youcandefineadraglisteneronjustaboutanythinginyourapplication,anditwillreceiveallofthedrageventsforallviewsinthesystem.Thiscanmakethingsalittleconfusingbecausethedraglistenerdoesnotneedtobeassociatedwitheithertheobjectbeingdraggedorthedroptarget.Thelistenercanmanageallofthecoordinationofthedraganddrop.

Infact,ifyouinspectthedrag-and-dropexampleprojectthatcomeswiththeAndroidSDK,youwillseethatitsetsupalisteneronaTextViewthathasnothingtodowiththeactualdragginganddropping.Theupcomingexampleprojectusesdraglistenersthataretiedtospecificviews.ThesedraglistenerseachreceiveaDragEventobjectforthedrageventsthatoccurinthedragsequence.ThismeansaviewcouldreceiveaDragEventobjectthatcanbeignoredbecauseitisreallyaboutadifferentview.ThisalsomeansthedraglistenermustmakethatdeterminationincodeandthattheremustbeenoughinformationwithintheDragEventobjectforthedraglistenertofigureoutwhattodo.

IfadraglistenergotaDragEventobjectthatmerelysaidthere’sanunknownobjectbeingdraggedandit’satcoordinates(15,57),thereisn’tmuchthedraglistenercandowithit.ItismuchmorehelpfultogetaDragEventobjectthatsaysaparticularobjectisbeingdragged,it’satcoordinates(15,57),it’sacopyoperation,andthedataisaspecificURI.Whenthatdrops,there’senoughinformationtobeabletoinitiateacopyoperation.

We’reactuallyseeingtwodifferentkindsofdragginggoingon.Inourfirstexampleapplication,wedraggedaviewacrossaframelayout,andwecouldletgoandthatviewwouldstaywhereitwas.Weonlygotdrag-and-dropbehaviorwhenwedroppedourviewontopofsomethingelse.Thesupportedformofdraganddropworksdifferentlythanthis.Now,whenyoudragaviewaspartofadrag-and-dropsequence,thedraggedviewdoesn’tmoveatall.Wegetashadowimageofthedraggedviewwhichdoestravelacrossthescreen,butifweletgoofit,thatshadowviewgoesaway.WhatthismeansisthatyoumightstillhaveoccasiontousethetechniquefromthebeginningofthischapterinanAndroid3.0+application,tomoveimagesaroundonthescreenperhaps,withoutnecessarilydoingdraganddrop.

Drag-and-DropExampleApplicationForyournextexampleapplication,you’regoingtoemployastapleof3.0,thefragment.This,amongotherthings,willprovethatdragscancrossfragmentboundaries.You’llcreateapaletteofdotsontheleftandasquaretargetontheright.Whenadotisgrabbedusingalongclick,you’llchangethecolorofthatdotinthepaletteandAndroidwillshowashadowofthedotasyoudrag.Whenthedraggeddotreachesthesquaretarget,thetargetwillbegintoglow.Ifyoudropthedotonthesquaretarget,amessagewillindicatethatyou’vejustaddedonemoredroptothedropcount,theglowingwillstop,andtheoriginaldotwillgobacktoitsoriginalcolor.

ListofFilesThisapplicationbuildsuponconceptswe’vecoveredthroughoutthisbook.We’reonlygoingtoincludetheinterestingfilesinthetext.Fortheothers,justlookattheminyourIDEatyourleisure.Herearetheonesthatwe’veincludedinthetext:

palette.xmlisthefragmentlayoutforthedotsontheleftside(seeListing23-3).

dropzone.xmlisthefragmentlayoutforthesquaretargetontherightside,plusthedrop-countmessage(seeListing23-4).

DropZone.javainflatesthedropzone.xmlfragmentlayoutfileandthenimplementsthedraglistenerforthedroptarget(seeListing23-5).

Dot.javaisyourcustomviewclassfortheobjectsyou’regoingtodrag.Ithandlesbeginningthedragsequence,watchingdragevents,anddrawingthedots(seeListing23-6).

LayingOuttheExampleDrag-and-DropApplicationBeforewegetintothecode,Figure23-2showswhattheapplicationwilllooklike.

Figure23-2.DragDropFragsexampleapplicationuserinterface

Themainlayoutfilehasasimplehorizontallinearlayoutandtwofragmentspecifications.Thefirstfragmentwillbeforthepaletteofdotsandthesecondwillbeforthedropzone.

Thepalettefragmentlayoutfile(Listing23-3)getsabitmoreinteresting.Althoughthislayoutrepresentsafragment,youdon’tneedtoincludeafragmenttagwithinthislayout.Thislayoutwillbeinflatedtobecometheviewhierarchyforyourpalettefragment.Thedotsarespecifiedascustomdots,andtherearetwoofthemarrangedvertically.NoticethatthereareacoupleofcustomXMLattributesinthedefinitionofyourdots(dot:coloranddot:radius).Asyoucansee,theseattributesspecifythecolorandtheradiusofyourdots.Youmightalsohavenoticedthatthelayoutwidthandheightarewrap_content,notmatch_parentasintheearlierexampleapplicationinthischapter.Thenewdrag-and-dropsupportmakesthingsmucheasier.

Listing23-3.Thepalette.xmlLayoutFilefortheDots

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/palette.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:dot="http://schemas.android.com/apk/res/com.androidbook.drag.drop.demoandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">

<com.androidbook.drag.drop.demo.Dotandroid:id="@+id/dot1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="30dp"android:tag="Bluedot"dot:color="#ff1111ff"dot:radius="20dp"/>

<com.androidbook.drag.drop.demo.Dotandroid:id="@+id/dot2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"android:tag="Whitedot"dot:color="#ffffffff"dot:radius="40dp"/>

</LinearLayout>

ThedropzonefragmentlayoutfileinListing23-4isalsoeasytounderstand.There’sagreensquareandatextmessagearrangedhorizontally.Thiswillbethedropzoneforthedotsyou’llbedragging.Thetextmessagewillbeusedtodisplayarunningcountofthedrops.

Listing23-4.Thedropzone.xmlLayoutFile

<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/dropzone.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal">

<Viewandroid:id="@+id/droptarget"android:layout_width="75dp"android:layout_height="75dp"android:layout_gravity="center_vertical"android:background="#00ff00"/>

<TextViewandroid:id="@+id/dropmessage"android:text="0drops"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:paddingLeft="50dp"android:textSize="17sp"/>

</LinearLayout>

RespondingtoonDragintheDropzoneNowthatyouhavethemainapplicationlayoutset,let’sseehowthedroptargetneedstobeorganizedbyexaminingListing23-5.

Listing23-5.TheDropZone.javaFile

publicclassDropZoneextendsFragment{

privateViewdropTarget;privateTextViewdropMessage;

@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,Bundleicicle){Viewv=inflater.inflate(R.layout.dropzone,container,false);

dropMessage=(TextView)v.findViewById(R.id.dropmessage);

dropTarget=(View)v.findViewById(R.id.droptarget);dropTarget.setOnDragListener(newView.OnDragListener(){privatestaticfinalStringDROPTAG="DropTarget";privateintdropCount=0;privateObjectAnimatoranim;

publicbooleanonDrag(Viewv,DragEventevent){intaction=event.getAction();booleanresult=true;switch(action){caseDragEvent.ACTION_DRAG_STARTED:Log.v(DROPTAG,"dragstartedindropTarget");break;caseDragEvent.ACTION_DRAG_ENTERED:Log.v(DROPTAG,"dragentereddropTarget");anim=ObjectAnimator.ofFloat((Object)v,"alpha",1f,0.5f);anim.setInterpolator(newCycleInterpolator(40));anim.setDuration(30*1000);//30secondsanim.start();break;

caseDragEvent.ACTION_DRAG_EXITED:Log.v(DROPTAG,"dragexiteddropTarget");if(anim!=null){anim.end();anim=null;}break;caseDragEvent.ACTION_DRAG_LOCATION:Log.v(DROPTAG,"dragproceedingindropTarget:"+event.getX()+","+event.getY());break;caseDragEvent.ACTION_DROP:Log.v(DROPTAG,"dragdropindropTarget");if(anim!=null){anim.end();anim=null;}

ClipDatadata=event.getClipData();Log.v(DROPTAG,"Itemdatais"+data.getItemAt(0).getText());

dropCount++;Stringmessage=dropCount+"drop";if(dropCount>1)message+="s";dropMessage.setText(message);break;caseDragEvent.ACTION_DRAG_ENDED:Log.v(DROPTAG,"dragendedindropTarget");if(anim!=null){anim.end();anim=null;}break;default:Log.v(DROPTAG,"otheractionindropzone:"+action);result=false;}returnresult;}});returnv;

}}

Nowyou’restartingtogetintointerestingcode.Forthedropzone,youneedtocreatethetargetuponwhichyouwanttodragthedots.Asyousawearlier,thelayoutspecifiesagreensquareonthescreenwithatextmessagenexttoit.Becausethedropzoneisalsoafragment,you’reoverridingtheonCreateView()methodofDropZone.Thefirstthingtodoisinflatethedropzonelayoutandthenextractouttheviewreferenceforthesquaretarget(dropTarget)andforthetextmessage(dropMessage).Thenyouneedtosetupadraglisteneronthetargetsoitwillknowwhenadragisunderway.

Thedrop-targetdraglistenerhasasinglecallbackmethodinit:onDrag().ThiscallbackwillreceiveaviewreferenceaswellasaDragEventobject.TheviewreferencerelatestotheviewthattheDragEventisrelatedto.Asmentioned,thedraglistenerisnotnecessarilyconnectedtotheviewthatwillbeinteractingwiththedragevent,sothiscallbackmustidentifytheviewforwhichthedrageventistakingplace.

OneofthefirstthingsyoulikelywanttodoinanyonDrag()callbackisreadtheactionfromtheDragEventobject.Thiswilltellyouwhat’sgoingon.Forthemostpart,theonlythingyouwanttodointhiscallbackislogthefactthatadrageventistakingplace.Youdon’tneedtoactuallydoanythingforACTION_DRAG_LOCATION,forexample.Butyoudowanttohavesomespeciallogicforwhentheobjectisdraggedwithinyourboundaries(ACTION_DRAG_ENTERED)thatwillbeturnedoffeitherwhentheobjectisdraggedoutsideofyourboundaries(ACTION_DRAG_EXITED)orwhentheobjectisdropped(ACTION_DROP).

You’reusingtheObjectAnimatorclassthatwasintroducedinChapter18,onlyhereyou’reusingitincodetospecifyacyclicinterpolatorthatmodifiesthetarget’salpha.Thiswillhavetheeffectofpulsingthetransparencyofthegreentargetsquare,whichwillbethevisualindicationthatthetargetiswillingtoacceptadropoftheobjectontoit.Becauseyouturnontheanimation,youmustmakesuretoalsoturnitoffwhentheobjectleavesorisdropped,orthedraganddropisended.Intheory,youshouldn’tneedtostoptheanimationonACTION_DRAG_ENDED,butit’swisetodoitanyway.

Forthisparticulardraglistener,you’regoingtogetACTION_DRAG_ENTEREDandACTION_DRAG_EXITEDonlyifthedraggedobjectinteractswiththeviewwithwhichyou’reassociated.Andasyou’llsee,theACTION_DRAG_LOCATIONeventshappenonlyifthedraggedobjectisinsideyourtargetview.

TheonlyotherinterestingconditionistheACTION_DROPitself(noticethatDRAG_isnotpartofthenameofthisaction).Ifadrophasoccurredonyourview,itmeanstheuserhasletgoofthedotoverthegreensquare.Becauseyou’reexpectingthisobjecttobedroppedonthegreensquare,youcanjustgoaheadandreadthedatafromthefirstitemandthenlogittoLogCat.Inaproductionapplication,youmightpaycloserattentiontotheClipDataobjectthatiscontainedinthedrageventitself.Byinspectingitsproperties,youcoulddecideifyouevenwanttoacceptthedropornot.

ThisisagoodtimetopointouttheresultbooleaninthisonDrag()callbackmethod.

Dependingonhowthingsgo,youwanttoletAndroidknoweitherthatyoutookcareofthedragevent(byreturningtrue)orthatyoudidn’t(byreturningfalse).Ifyoudon’tseewhatyouwanttoseeinsideofthedrageventobject,youcouldcertainlyreturnfalsefromthiscallback,whichwouldtellAndroidthatthisdropwasnothandled.

OnceyoulogtheinformationfromthedrageventinLogCat,youincrementthecountofthedropsreceived;thisisupdatedintheuserinterface,andthat’saboutitforDropZone.

Ifyoulookthisclassover,it’sreallyrathersimple.Youdon’tactuallyhaveanycodeinherethatdealswithMotionEvents,nordoyouevenneedtomakeyourowndeterminationofwhetherthereisadraggoingon.Youjustgetappropriatecallbackcallsasadragsequenceunfolds.

SettingUptheDragSourceViewsLet’snowconsiderhowviewscorrespondingtoadragsourceareorganized,startingbylookingatListing23-6.

Listing23-6.TheJavafortheCustomView:Dot

publicclassDotextendsViewimplementsView.OnDragListener{privatestaticfinalintDEFAULT_RADIUS=20;privatestaticfinalintDEFAULT_COLOR=Color.WHITE;privatestaticfinalintSELECTED_COLOR=Color.MAGENTA;protectedstaticfinalStringDOTTAG="DragDot";privatePaintmNormalPaint;privatePaintmDraggingPaint;privateintmColor=DEFAULT_COLOR;privateintmRadius=DEFAULT_RADIUS;privatebooleaninDrag;

publicDot(Contextcontext,AttributeSetattrs){super(context,attrs);

//Applyattributesettingsfromthelayoutfile.//Note:thesecouldchangeonareconfiguration//suchasascreenrotation.TypedArraymyAttrs=context.obtainStyledAttributes(attrs,R.styleable.Dot);

finalintnumAttrs=myAttrs.getIndexCount();for(inti=0;i<numAttrs;i++){intattr=myAttrs.getIndex(i);switch(attr){caseR.styleable.Dot_radius:

mRadius=myAttrs.getDimensionPixelSize(attr,DEFAULT_RADIUS);break;caseR.styleable.Dot_color:mColor=myAttrs.getColor(attr,DEFAULT_COLOR);break;}}myAttrs.recycle();

//SetuppaintcolorsmNormalPaint=newPaint();mNormalPaint.setColor(mColor);mNormalPaint.setAntiAlias(true);

mDraggingPaint=newPaint();mDraggingPaint.setColor(SELECTED_COLOR);mDraggingPaint.setAntiAlias(true);

//StartadragonalongclickonthedotsetOnLongClickListener(lcListener);setOnDragListener(this);}

privatestaticView.OnLongClickListenerlcListener=newView.OnLongClickListener(){privatebooleanmDragInProgress;

publicbooleanonLongClick(Viewv){ClipDatadata=ClipData.newPlainText("DragData",(String)v.getTag());

mDragInProgress=v.startDrag(data,newView.DragShadowBuilder(v),(Object)v,0);

Log.v((String)v.getTag(),"startingdrag?"+mDragInProgress);

returntrue;}};

@OverrideprotectedvoidonMeasure(intwidthSpec,intheightSpec){intsize=2*mRadius+getPaddingLeft()+getPaddingRight();

setMeasuredDimension(size,size);}

//ThedraggingfunctionalitypublicbooleanonDrag(Viewv,DragEventevent){StringdotTAG=(String)getTag();//Onlyworryaboutdrageventsifthisisusbeingdraggedif(event.getLocalState()!=this){Log.v(dotTAG,"Thisdrageventisnotforus");returnfalse;}booleanresult=true;

//geteventvaluestoworkwithintaction=event.getAction();floatx=event.getX();floaty=event.getY();

switch(action){caseDragEvent.ACTION_DRAG_STARTED:Log.v(dotTAG,"dragstarted.X:"+x+",Y:"+y);inDrag=true;//usedindraw()belowtochangecolorbreak;caseDragEvent.ACTION_DRAG_LOCATION:Log.v(dotTAG,"dragproceeding…At:"+x+","+y);break;caseDragEvent.ACTION_DRAG_ENTERED:Log.v(dotTAG,"dragentered.At:"+x+","+y);break;caseDragEvent.ACTION_DRAG_EXITED:Log.v(dotTAG,"dragexited.At:"+x+","+y);break;caseDragEvent.ACTION_DROP:Log.v(dotTAG,"dragdropped.At:"+x+","+y);//Returnfalsebecausewedon'tacceptthedropinDot.result=false;break;caseDragEvent.ACTION_DRAG_ENDED:Log.v(dotTAG,"dragended.Success?"+event.getResult());

inDrag=false;//changecoloroforiginaldotbackbreak;default:Log.v(dotTAG,"someotherdragaction:"+action);result=false;break;}returnresult;}

//Hereiswhereyoudrawourdot,andwhereyouchangethecolorif//you'reintheprocessofbeingdragged.Note:thecolorchange//affectstheoriginaldotonly,nottheshadow.publicvoiddraw(Canvascanvas){floatcx=this.getWidth()/2+getLeftPaddingOffset();floatcy=this.getHeight()/2+getTopPaddingOffset();Paintpaint=mNormalPaint;if(inDrag)paint=mDraggingPaint;canvas.drawCircle(cx,cy,mRadius,paint);invalidate();}}

TheDotcodelookssomewhatsimilartothecodeforDropZone.Thisisinpartbecauseyou’realsoreceivingdrageventsinthisclass.TheconstructorforaDotfiguresouttheattributesinordertosetthecorrectradiusandcolor,andthenitsetsupthetwolisteners:oneforlongclicksandanotherforthedragevents.

Thetwopaintsaregoingtobeusedtodrawyourcircle.Youusethenormalpaintwhenthedotisjustsittingthere.Butwhenthedotisbeingdragged,youwanttoindicatethatbychangingthecoloroftheoriginaltomagenta.

Thelong-clicklisteneriswhereyouinitiateadragsequence.Theonlywayyoulettheuserstartdraggingadotisiftheuserclicksandholdsonadot.Whenthelong-clicklistenerisfiring,youcreateanewClipDataobjectusingastringandthedot’stag.YouhappentoknowthatthetagisthenameofthedotasspecifiedintheXMLlayoutfile.ThereareseveralotherwaystospecifydataintoaClipDataobject,sofeelfreetoreadthereferencedocumentationonotherwaystostoredatainaClipDataobject.

Thenextstatementisthecriticalone:startDrag().ThisiswhereAndroidwilltakeoverandstarttheprocessofdragging.NotethatthefirstargumentistheClipDataobjectfrombefore;thenit’sthedrag-shadowobject,thenalocal-stateobject,andfinallythenumberzero.

Thedrag-shadowobjectistheimagethatwillbedisplayedasthedraggingistakingplace.Inyourcase,thisdoesnotreplacetheoriginaldotimageonthescreenbutshowsashadowofadotasthedraggingistakingplace,inadditiontotheoriginaldotonthescreen.ThedefaultDragShadowBuilderbehavioristocreateashadowthatlooksverymuchliketheoriginal,soforyourpurposes,youmerelycallitandpassinyourview.Youcangetfancyhereandcreatewhateversortofshadowviewyouwant,butifyoudooverridethisclass,you’llneedtoimplementafewmethodstomakeitwork.

TheonMeasure()methodisheretosupplydimensioninformationtoAndroidforthecustomviewyou’reusinghere.YouhavetotellAndroidhowbigyourviewissoitknowshowtolayitoutwitheverythingelse.Thisisstandardpracticeforacustomview.

Finally,there’stheonDrag()callback.Asmentioned,eachdraglistenercanreceivedragevents.TheyallgetACTION_DRAG_STARTEDandACTION_DRAG_ENDED,forexample.So,wheneventshappen,youmustbecarefulwhatyoudowiththeinformation.Becausetherearetwodotsinplayinthisexampleapplication,wheneveryoudosomethingwiththedots,youmustbecarefulthatyou’reaffectingthecorrectone.

WhenbothdotsreceivetheACTION_DRAG_STARTEDaction,onlyoneshouldsetthecolorofitselftomagenta.Tofigureoutwhichoneiscorrect,comparethelocalstateobjectpassedinwithyourself.Ifyoulookbackwhereyousetthelocal-stateobject,youpassedthecurrentviewin.Sonow,whenyou’vereceivedthelocal-stateobjectout,youcompareittoyourselftoseeifyou’retheviewthatinitiatedthedragsequence.

Ifyouaren’tthesameview,youwritealogmessagetoLogCatsayingthisisnotforyou,andyoureturnfalsetosayyou’renothandlingthismessage.

Ifyouaretheviewthatshouldbereceivingthisdragevent,youcollectsomevaluesfromthedragevent,thenyoumostlyjustlogtheeventtoLogCat.ThefirstexceptiontothisisACTION_DRAG_STARTED.Ifyougotthisactionandit’sforyou,youthenknowthatyourdothasbegunadragsequence.Therefore,yousettheinDragbooleansothedraw()methodlateronwilldotherightthinganddisplayadifferent-coloreddot.ThisdifferentcoloronlylastsuntilACTION_DRAG_ENDEDisreceived,atwhichtimeyourestoretheoriginalcolorofthedot.

IfadotgetstheACTION_DROPaction,thismeanstheusertriedtodropadotonadot—maybeeventheoriginaldot.Thisshouldn’tdoanything,soyoujustreturnfalsefromthiscallbackinthiscase.

Finally,thedraw()methodofyourcustomviewfiguresoutthelocationofthecenterpointofyourcircle(dot)andthendrawsitwiththeappropriatepaint.Theinvalidate()methodistheretotellAndroidthatyou’vemodifiedtheviewandthatAndroidshouldredrawtheuserinterface.Bycallinginvalidate(),youensurethattheuserinterfacewillbeupdatedveryshortlywithwhateverisnew.

Younowhaveallthefilesandthebackgroundnecessarytocompileanddeploythisexampledrag-and-dropapplication.

TestingtheExampleDrag-and-DropApplicationFollowingissomeexampleoutputfromLogCatwhenweranthisexampleapplication.NoticehowthelogmessageusedBluedottoindicatemessagesfromthebluedot,Whitedotformessagesfromthewhitedot,andDropTargetfortheviewwherethedropsareallowedtogo.

Whitedot:startingdrag?trueBluedot:ThisdrageventisnotforusWhitedot:dragstarted.X:53.0,Y:206.0DropTarget:dragstartedindropTargetDropTarget:dragentereddropTargetDropTarget:dragproceedingindropTarget:29.0,36.0DropTarget:dragproceedingindropTarget:48.0,39.0DropTarget:dragproceedingindropTarget:45.0,39.0DropTarget:dragproceedingindropTarget:41.0,39.0DropTarget:dragproceedingindropTarget:40.0,39.0DropTarget:dragdropindropTargetDropTarget:ItemdataisWhitedotViewRoot:Reportingdropresult:trueWhitedot:dragended.Success?trueBluedot:ThisdrageventisnotforusDropTarget:dragendedindropTarget

Inthisparticularcase,thedragwasstartedwiththewhitedot.Oncethelongclickhastriggeredthebeginningofthedragsequence,wegetthestartingdragmessage.

NoticehowthenextthreelinesallindicatethatanACTION_DRAG_STARTEDactionwasreceivedinthreedifferentviews.Bluedotdeterminedthatthecallbackwasnotforit.ItwasalsonotforDropTarget.

Next,noticehowthedrag-proceedingmessagesshowthedraghappeningthroughDropTarget,beginningwiththeACTION_DRAG_ENTEREDaction.Thismeansthedotwasbeingdraggedontopofthegreensquare.Thexandycoordinatesreportedinthedrageventobjectarethecoordinatesofthedragpointrelativetotheupper-leftcorneroftheview.So,intheexampleapp,thefirstrecordofthedraginthedroptargetisat(x,y)=(29,36),andthedropoccurredat(40,39).Seehowthedroptargetwasabletoextractthetagnameofthewhitedotfromtheevent’sClipDatatowriteittoLogCat.

Alsoseehowonceagain,alldraglistenersreceivedtheACTION_DRAG_ENDEDaction.OnlyWhitedotdeterminedthatit’sokaytodisplaytheresultsusinggetResult().

Feelfreetoexperimentwiththisexampleapplication.Dragadottotheotherdot,oreventoitself.Goaheadandaddanotherdottopalette.xml.Noticehowwhenthedragged

dotleavesthegreensquare,there’samessagesayingthatthedragexited.Notealsothatifyoudropadotsomewhereotherthanthegreensquare,thedropisconsideredfailed.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch23_DragnDrop.zip.Thiszipfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneofthesezipfiles.

http://developer.android.com/guide/topics/ui/drag-drop.html:TheAndroiddeveloper’sguidetodraganddrop.

SummaryLet’ssummarizethetopicscoveredinthischapter:

Drag-and-dropsupportinAndroid3.0,andimplementingitpriorto3.0usingothermethods

Iteratingthroughpossibledroptargetstoseeifadrop(thatis,fingerleavingthescreenafterdragging)occurred

Thedifficultyofdoingthemathtokeeptrackofwhereadraggedobjectisandwhetherit’soveradroptarget

Drag-and-dropsupportinAndroid3.0+,whichismuchnicerbecauseiteliminatesalotofguesswork

Draglisteners,whichcanbeanyobjectsanddonotneedtobedraggablesordrop-targetviews

Thefactthatadragcanoccuracrossfragments

TheDragEventobject,whichcancontainlotsofgreatinformationaboutwhatisbeingdraggedandwhy

HowAndroidtakescareofthemathtodeterminewhetheradropisoccurringontopofaview

Chapter24

UsingSensorsAndroiddevicesoftencomewithhardwaresensorsbuiltin,andAndroidprovidesaframeworkforworkingwiththosesensors.Sensorscanbefun.Measuringtheoutsideworldandusingthatinsoftwareinadeviceisprettycool.Itisthekindofprogrammingexperienceyoujustdon’tgetonaregularcomputerthatsitsonadeskorinaserverroom.Thepossibilitiesfornewapplicationsthatusesensorsarehuge,andwehopeyouareinspiredtorealizethem.

Inthischapter,we’llexploretheAndroidsensorframework.We’llexplainwhatsensorsareandhowwegetsensordata,andthendiscusssomespecificsofthekindsofdatawecangetfromsensorsandwhatwecandowithit.WhileAndroidhasdefinedseveralsensortypesalready,therearenodoubtmoresensorsinAndroid’sfuture,andweexpectthatfuturesensorswillgetincorporatedintothesensorframework.

WhatIsaSensor?InAndroid,asensorisasourceofdataeventsfromthephysicalworld.Thisistypicallyapieceofhardwarethathasbeenwiredintothedevice,butAndroidalsoprovidessomelogicalsensorsthatcombinedatafrommultiplephysicalsensors.Applicationsinturnusethesensordatatoinformtheuseraboutthephysicalworld,tocontrolgameplay,todoaugmentedreality,ortoprovideusefultoolsforworkingintherealworld.Sensorsoperateinonedirectiononly;they’reread-only.Thatmakesusingthemfairlystraightforward.Yousetupalistenertoreceivesensordata,andthenyouprocessthedataasitcomesin.GPShardwareislikethesensorswecoverinthischapter.InChapter19,wesetuplistenersforGPSlocationupdates,andweprocessedthoselocationupdatesastheycamein.ButalthoughGPSissimilartoasensor,itisnotpartofthesensorframeworkthatisprovidedbyAndroid.

SomeofthesensortypesthatcanappearinanAndroiddeviceinclude

Lightsensor

Proximitysensor

Temperaturesensor

Pressuresensor

Gyroscopesensor

Accelerometer

Magneticfieldsensor

Gravitysensor

Linearaccelerationsensor

Rotationvectorsensor

Relativehumiditysensor

DetectingSensorsPleasedon’tassume,however,thatallAndroiddeviceshaveallofthesesensors.Infact,manydeviceshavejustsomeofthesesensors.TheAndroidemulator,forexample,hasonlyanaccelerometer.Sohowdoyouknowwhichsensorsareavailableonadevice?Therearetwoways,onedirectandoneindirect.

ThefirstwayisthatyouasktheSensorManagerforalistoftheavailablesensors.Itwillrespondwithalistofsensorobjectsthatyoucanthensetuplistenersforandgetdatafrom.We’llshowyouhowabitlaterinthischapter.Thismethodassumesthattheuserhasalreadyinstalledyourapplicationontoadevice,butwhatifthedevicedoesn’thaveasensorthatyourapplicationneeds?

That’swherethesecondmethodcomesin.WithintheAndroidManifest.xmlfile,youcanspecifythefeaturesadevicemusthaveinordertoproperlysupportyourapplication.Ifyourapplicationneedsaproximitysensor,youspecifythatinyourmanifestfilewithalinesuchasthefollowing:

<uses-featureandroid:name="android.hardware.sensor.proximity"/>

TheGooglePlayStorewillonlyinstallyourapponadevicethathasaproximitysensor,soyouknowit’stherewhenyourapplicationruns.ThesamecannotbesaidforallotherAndroidappstores.Thatis,someAndroidappstoresdonotperformthatkindofchecktomakesureyourappcanonlybeinstalledontoadevicethatsupportsthesensorsyouspecify.

WhatCanWeKnowAboutaSensor?Whileusingtheuses-featuretagsinthemanifestfileletsyouknowthatasensoryourapplicationrequiresexistsonadevice,itdoesn’ttellyoueverythingyoumaywanttoknowabouttheactualsensor.Let’sbuildasimpleapplicationthatqueriesthedeviceforsensorinformation.Listing24-1showstheJavacodeofourMainActivity.

Note

Youcandownloadthischapter’sprojects.WewillgiveyoutheURLattheendofthechapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.

Listing24-1.JavaforaSensorListApp

publicclassMainActivityextendsActivity{@Override

publicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

TextViewtext=(TextView)findViewById(R.id.text);

SensorManagermgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);

List<Sensor>sensors=mgr.getSensorList(Sensor.TYPE_ALL);

StringBuildermessage=newStringBuilder(2048);message.append("Thesensorsonthisdeviceare:\n");

for(Sensorsensor:sensors){message.append(sensor.getName()+"\n");message.append("Type:"+sensorTypes.get(sensor.getType())+"\n");message.append("Vendor:"+sensor.getVendor()+"\n");message.append("Version:"+sensor.getVersion()+"\n");try{message.append("MinDelay:"+sensor.getMinDelay()+"\n");}catch(NoSuchMethodErrore){}//ignoreifnotfoundtry{message.append("FIFOMaxEventCount:"+sensor.getFifoMaxEventCount()+"\n");}catch(NoSuchMethodErrore){}//ignoreifnotfoundmessage.append("Resolution:"+sensor.getResolution()+"\n");message.append("MaxRange:"+sensor.getMaximumRange()+"\n");message.append("Power:"+sensor.getPower()+"mA\n");}text.setText(message);}

privateHashMap<Integer,String>sensorTypes=newHashMap<Integer,String>();

{sensorTypes.put(Sensor.TYPE_ACCELEROMETER,"TYPE_ACCELEROMETER");sensorTypes.put(Sensor.TYPE_AMBIENT_TEMPERATURE,"TYPE_AMBIENT_TEMPERATURE");/*...therestisomittedtosavespace…*/}}

WithinouronCreate()method,westartbygettingareferencetotheSensorManager.Therecanbeonlyoneofthese,soweretrieveitasasystemservice.WethencallitsgetSensorList()methodtogetalistofsensors.Foreachsensor,wewriteoutinformationaboutit.TheoutputwilllooksomethinglikeFigure24-1.

Figure24-1.Outputfromoursensorlistapp

Thereareafewthingstoknowaboutthissensorinformation.Thetypevaluetellsyouthebasictypeofthesensorwithoutgettingspecific.Alightsensorisalightsensor,butyoucouldgetvariationsinlightsensorsfromonedevicetoanother.Forexample,theresolutionofalightsensorononedevicecouldbedifferentfromthatonanotherdevice.

Whenyouspecifythatyourappneedsalightsensorina<uses-feature>tag,youdon’tknowinadvanceexactlywhattypeoflightsensoryou’regoingtoget.Ifitmatterstoyourapplication,you’llneedtoquerythedevicetofindoutandadjustyourcodeaccordingly.Thevaluesyougetforresolutionandmaximumrangewillbeintheappropriateunitsforthatsensor.Thepowermeasurementisinmilliamperes(mA)andrepresentstheelectricalcurrentthatthesensordrawsfromthedevice’sbattery;smallerisbetter.

Nowthatweknowwhatsensorswehaveavailabletous,howdowegoaboutgettingdatafromthem?Asweexplainedearlier,wesetupalistenerinordertogetsensordatasenttous.Let’sexplorethatnow.

GettingSensorEventsSensorsprovidedatatoourapplicationonceweregisteralistenertoreceivethedata.Whenourlistenerisnotlistening,thesensorcanbeturnedoff,conservingbatterylife,somakesureyouonlylistenwhenyoureallyneedto.Settingupasensorlisteneriseasytodo.Let’ssaythatwewanttomeasurethelightlevelsfromthelightsensor.Listing24-2showstheJavacodeforasampleappthatdoesthis.

Listing24-2.JavaCodeforaLightSensorMonitorApp

publicclassMainActivityextendsActivityimplementsSensorEventListener{privateSensorManagermgr;privateSensorlight;privateTextViewtext;privateStringBuildermsg=newStringBuilder(2048);

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

mgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);light=mgr.getDefaultSensor(Sensor.TYPE_LIGHT);text=(TextView)findViewById(R.id.text);}

@OverrideprotectedvoidonResume(){mgr.registerListener(this,light,SensorManager.SENSOR_DELAY_NORMAL);super.onResume();}

@OverrideprotectedvoidonPause(){mgr.unregisterListener(this,light);super.onPause();}

publicvoidonAccuracyChanged(Sensorsensor,intaccuracy){msg.insert(0,sensor.getName()+"accuracychanged:"+accuracy+(accuracy==1?"(LOW)":(accuracy==2?"(MED)":"(HIGH)"))+"\n");text.setText(msg);text.invalidate();}

publicvoidonSensorChanged(SensorEventevent){msg.insert(0,"Gotasensorevent:"+event.values[0]+"SIluxunits\n");text.setText(msg);text.invalidate();}}

Inthissampleapp,weagaingetareferencetotheSensorManager,butinsteadofgettingalistofsensors,wequeryspecificallyforthelightsensor.WethensetupalistenerintheonResume()methodofouractivity,andweunregisterthelistenerintheonPause()method.Wedon’twanttobeworryingaboutthelightlevelswhenourapplicationisnotintheforeground.

FortheregisterListener()method,wepassinavaluerepresentinghowoftenwewanttobenotifiedofsensorvaluechanges.Thisparametercouldbe

SENSOR_DELAY_NORMAL(represents200,000microseconddelay)

SENSOR_DELAY_UI(represents60,000microseconddelay)

SENSOR_DELAY_GAME(represents20,000microseconddelay)

SENSOR_DELAY_FASTEST(representsasfastaspossible)

YoucanalsospecifyaspecificmicroseconddelayusingoneoftheotherregisterListener

methods,aslongasit’slargerthan3microseconds;howeveranythinglessthan20,000isnotlikelytobehonored.Itisimportanttoselectanappropriatevalueforthisparameter.Somesensorsareverysensitiveandwillgeneratealotofeventsinashortamountoftime.IfyouchooseSENSOR_DELAY_FASTEST,youmightevenoverrunyourapplication’sabilitytokeepup.Dependingonwhatyourapplicationdoeswitheachsensorevent,itispossiblethatyouwillbecreatinganddestroyingsomanyobjectsinmemorythatgarbagecollectionwillcausenoticeableslowdownsandhiccupsonthedevice.Ontheotherhand,certainsensorsprettymuchdemandtobereadasoftenaspossible;thisistrueoftherotationvectorsensorinparticular.Also,don’trelyonthisparametertogenerateeventswithprecisetiming.Theeventscouldcomealittlefasterorslower.

BecauseouractivityimplementstheSensorEventListenerinterface,wehavetwocallbacksforsensorevents:onAccuracyChanged()andonSensorChanged().Thefirstmethodwillletusknowiftheaccuracychangesonoursensor(orsensors,sinceitcouldbecalledformorethanone).ThevalueoftheaccuracyparameterwillbeSENSOR_STATUS_UNRELIABLE,SENSOR_STATUS_ACCURACY_LOW,SENSOR_STATUS_ACCURACY_MEDIUM,orSENSOR_STATUS_ACCURACY_HIGH.Unreliableaccuracydoesnotmeanthatthedeviceisbroken;itnormallymeansthatthesensorneedstobecalibrated.Thesecondcallbackmethodtellsuswhenthelightlevelhaschanged,andwegetaSensorEventobjecttotellusthedetailsofthenewvalueorvaluesfromthesensor.

ASensorEventobjecthasseveralmembers,oneofthembeinganarrayoffloatvalues.Foralightsensorevent,onlythefirstfloatvaluehasmeaning,whichistheSIluxvalueofthelightthatwasdetectedbythesensor.Foroursampleapp,webuildupamessagestringbyinsertingthenewmessagesontopoftheoldermessages,andthenwedisplaythebatchofmessagesinaTextView.Ournewestsensorvalueswillalwaysbedisplayedatthetopofthescreen.

Whenyourunthisapplication(onarealdevice,ofcourse,sincetheemulatordoesnothavealightsensor),youmaynoticethatnothingisdisplayedatfirst.Justchangethelightthatisshiningontheupper-leftcornerofyourdevice.Thisismostlikelywhereyourlightsensoris.Ifyoulookverycarefully,youmightseethedotbehindthescreenthatisthelightsensor.Ifyoucoverthisdotwithyourfinger,thelightlevelwillprobablychangetoaverysmallvalue(althoughitmaynotreachzero).Themessagesshoulddisplayonthescreen,tellingyouaboutthechanginglightlevels.

Note

Youmightalsonoticethatwhenthelightsensoriscovered,yourbuttonslightup(ifyouhaveadevicewithlightedbuttons).ThisisbecauseAndroidhasdetectedthedarknessandlightsupthebuttonstomakethedeviceeasiertouse“inthedark.”

IssueswithGettingSensorDataTheAndroidsensorframeworkhasproblemsthatyouneedtobeawareof.Thisisthepartthat’snotfun.Insomecases,wehavewaysofworkingaroundtheproblem;inotherswe

don’t,orit’sverydifficult.

NoDirectAccesstoSensorValuesYoumayhavenoticedthatthereisnodirectwaytoquerythesensor’scurrentvalue.Theonlywaytogetdatafromasensoristhroughalistener.Therearetwokindsofsensors:thosethatarestreamingandthosethatarenot.Streamingsensorswillsendvaluesonaregularbasis,suchastheaccelerometer.ThemethodcallgetMinDelay()willreturnanonzerovalueforstreamingsensors,totellyoutheminimumnumberofmicrosecondsthatasensorwillusetosensetheenvironment.Fornon-streamingsensorsthereturnvalueiszero,soevenonceyou’vesetupthelistener,therearenoguaranteesthatyou’llgetanewdatumwithinasetperiodoftime.Atleastthecallbackisasynchronoussoyouwon’tblocktheUIthreadwaitingforapieceofdatafromasensor.However,yourapplicationhastoaccommodatethefactthatsensordatamaynotbeavailableattheexactmomentthatyouwantit.RevisitingFigure24-1,you’llnoticethatthelightsensorisnon-streaming.Therefore,yourappwillgetaneventonlyifthelightlevelchanges.Fortheothersensorsshown,thedelaybetweeneventswillbeaminimumof20milliseconds,butcouldbemore.

ItispossibletodirectlyaccesssensorsusingnativecodeandtheJNIfeatureofAndroid.You’llneedtoknowthelow-levelnativeAPIcallsforthesensordriveryou’reinterestedin,plusbeabletosetuptheinterfacebacktoAndroid.Soitcanbedone,butit’snoteasy.

SensorValuesNotSentFastEnoughEvenatSENSOR_DELAY_FASTEST,youprobablywon’tgetnewvaluesmoreoftenthanevery20ms(itdependsonthedeviceandthesensor).IfyouneedmorerapidsensordatathanyoucangetwitharatesettingofSENSOR_DELAY_FASTEST,itispossibletousenativecodeandJNItogettothesensordatafaster,butsimilartotheprevioussituation,itisnoteasy.

SensorsTurnOffwiththeScreenTherehavebeenproblemsinAndroid2.xwithsensorupdatesthatgetturnedoffwhenthescreenisturnedoff.Apparentlysomeonethoughtitwasagoodideatonotsendsensorupdatesifthescreenisoff,evenifyourapplication(mostlikelyusingaservice)hasawakelock.Basically,yourlistenergetsunregisteredwhenthescreenturnsoff.

Thereareseveralworkaroundstothisproblem.Formoreinformationonthisissueandpossibleresolutionsandworkarounds,pleaserefertoAndroidIssue11028:

http://code.google.com/p/android/issues/detail?id=11028

Nowthatyouknowhowtogetdatafromsensors,whatcanyoudowiththedata?Aswesaidearlier,dependingonwhichsensoryou’regettingdatafrom,thevaluesreturnedinthevaluesarraymeandifferentthings.Thenextsectionwillexploreeachofthesensortypesandwhattheirvaluesmean.

InterpretingSensorDataNowthatyouunderstandhowtogetdatafromasensor,you’llwanttodosomethingmeaningfulwiththedata.Thedatayouget,however,willdependonwhichsensoryou’regettingthedatafrom.Somesensorsaresimplerthanothers.Inthesectionsthatfollow,wewilldescribethedatathatyou’llgetfromthesensorswecurrentlyknowabout.Asnewdevicescomeintobeing,newsensorswillundoubtedlybeintroducedaswell.Thesensorframeworkisverylikelytoremainthesame,sothetechniquesweshowhereshouldapplyequallywelltothenewsensors.

LightSensorsThelightsensorisoneofthesimplestsensorsonadevice,andoneyou’veusedinthefirstsampleapplicationsofthischapter.Thesensorgivesareadingofthelightleveldetectedbythelightsensorofthedevice.Asthelightlevelchanges,thesensorreadingschange.TheunitsofthedataareinSIluxunits.Tolearnmoreaboutwhatthismeans,pleaseseethe“References”sectionattheendofthischapterforlinkstomoreinformation.

ForthevaluesarrayintheSensorEventobject,alightsensorusesjustthefirstelement,values[0].Thisvalueisafloatandrangestechnicallyfrom0tothemaximumvaluefortheparticularsensor.Wesaytechnicallybecausethesensormayonlysendverysmallvalueswhenthere’snolight,andneveractuallysendavalueof0.

Rememberalsothatthesensorcantellusthemaximumvaluethatitcanreturnandthatdifferentsensorscanhavedifferentmaximums.Forthisreason,itmaynotbeusefultoconsiderthelight-relatedconstantsintheSensorManagerclass.Forexample,SensorManagerhasaconstantcalledLIGHT_SUNLIGHT_MAX,whichisafloatvalueof120,000;however,whenwequeriedourdeviceearlier,themaximumvaluereturnedwas10,240,clearlymuchlessthanthisconstantvalue.There’sanotheronecalledLIGHT_SHADEat20,000,whichisalsoabovethemaximumofthedevicewetested.Sokeepthisinmindwhenwritingcodethatuseslightsensordata.

ProximitySensorsTheproximitysensoreithermeasuresthedistancethatsomeobjectisfromthedevice(incentimeters)orrepresentsaflagtosaywhetheranobjectiscloseorfar.Someproximitysensorswillgiveavaluerangingfrom0.0tothemaximuminincrements,whileothersreturneither0.0orthemaximumvalueonly.Ifthemaximumrangeoftheproximitysensorisequaltothesensor’sresolution,thenyouknowit’soneofthosethatonlyreturns0.0,orthemaximum.Therearedeviceswithamaximumof1.0andotherswhereit’s6.0.Unfortunately,there’snowaytotellbeforetheapplicationisinstalledandrunwhichproximitysensoryou’regoingtoget.Evenifyouputa<uses-feature>taginyourAndroidManifest.xmlfilefortheproximitysensor,youcouldgeteitherkind.Unlessyouabsolutelyneedtohavethemoregranularproximitysensor,yourapplicationshouldaccommodatebothtypesgracefully.

Here’saninterestingfactaboutproximitysensors:theproximitysensorissometimesthesamehardwareasthelightsensor.Androidstilltreatsthemaslogicallyseparatesensors,though,soifyouneeddatafrombothyouwillneedtosetupalistenerforeachone.Here’sanotherinterestingfact:theproximitysensorisoftenusedinthephoneapplicationtodetectthepresenceofaperson’sheadnexttothedevice.Iftheheadisthatclosetothetouchscreen,thetouchscreenisdisabledsonokeyswillbeaccidentlypressedbytheearorcheekwhilethepersonistalkingonthephone.

Thesourcecodeprojectsforthischapterincludeasimpleproximitysensormonitorapplication,whichisbasicallythelightsensormonitorapplicationmodifiedtousetheproximitysensorinsteadofthelightsensor.Wewon’tincludethecodeinthischapter,butfeelfreetoexperimentwithitonyourown.

TemperatureSensorsTheolddeprecatedtemperaturesensor(TYPE_TEMPERATURE)providedatemperaturereadingandalsoreturnedjustasinglevalueinvalues[0].Thissensorusuallyreadaninternaltemperature,suchasatthebattery.ThereisanewtemperaturesensorcalledTYPE_AMBIENT_TEMPERATURE.ThenewvaluerepresentsthetemperatureoutsidethedeviceindegreesCelsius.

Theplacementofthetemperaturesensorisdevice-dependent,anditispossiblethatthetemperaturereadingscouldbeimpactedbytheheatgeneratedbythedeviceitself.TheprojectsforthischapterincludeoneforthetemperaturesensorcalledTemperatureSensor.IttakescareofcallingthecorrecttemperaturesensorbasedonwhichversionofAndroidisrunning.

PressureSensorsThissensormeasuresbarometricpressure,whichcoulddetectaltitudeforexampleorbeusedforweatherpredictions.ThissensorshouldnotbeconfusedwiththeabilityofatouchscreentogenerateaMotionEventwithapressurevalue(thepressureofthetouch).WecoveredthistouchtypeofpressuresensinginChapter22.Touchscreenpressuresensingdoesn’tusetheAndroidsensorframework.

TheunitofmeasurementforapressuresensorisatmosphericpressureinhPa(millibar),andthismeasurementisdeliveredinvalues[0].

GyroscopeSensorsGyroscopesareverycoolcomponentsthatcanmeasurethetwistofadeviceaboutareferenceframe.Saidanotherway,gyroscopesmeasuretherateofrotationaboutanaxis.Whenthedeviceisnotrotating,thesensorvalueswillbezeros.Whenthereisrotationinanydirection,you’llgetnonzerovaluesfromthegyroscope.Gyroscopesareoftenusedfornavigation.Butbyitself,agyroscopecan’ttellyoueverythingyouneedtoknowtonavigate.Andunfortunately,errorscreepinovertime.Butcoupledwithaccelerometers,youcandeterminethepathofmovementofthedevice.

Kalmanfilterscanbeusedtolinkdatafromthetwosensorstogether.Accelerometersarenotterriblyaccurateintheshortterm,andgyroscopesarenotveryaccurateinthelongterm,socombinedtheycanbereasonablyaccurateallthetime.WhileKalmanfiltersareverycomplex,thereisanalternativecalledcomplementaryfiltersthatareeasiertoimplementincodeandproduceresultsthatareprettygood.Theseconceptsarebeyondthescopeofthisbook.

Thegyroscopesensorreturnsthreevaluesinthevaluesarrayforthex,y,andzaxes.Theunitsareradianspersecond,andthevaluesrepresenttherateofrotationaroundeachofthoseaxes.Onewaytoworkwiththesevaluesistointegratethemovertimetocalculateananglechange.Thisisasimilarcalculationtointegratinglinearspeedovertimetocalculatedistance.

AccelerometersAccelerometersareprobablythemostutilizedofthesensorsonadevice.Usingthesesensors,yourapplicationcandeterminethephysicalorientationofthedeviceinspacerelativetogravity’spullstraightdown,plusbeawareofforcesactingonthedevice.Providingthisinformationallowsanapplicationtodoallsortsofinterestingthings,fromgameplaytoaugmentedreality.Andofcourse,theaccelerometerstellAndroidwhentoswitchtheorientationoftheuserinterfacefromportraittolandscapeandbackagain.

Theaccelerometercoordinatesystemworkslikethis:theaccelerometer’sxaxisoriginatesinthebottom-leftcornerofthedeviceandgoesacrossthebottomtotheright.Theyaxisalsooriginatesinthebottom-leftcornerandgoesupalongtheleftofthedisplay.Thezaxisoriginatesinthebottom-leftcornerandgoesupinspaceawayfromthedevice.Figure24-2showswhatthismeans.

Figure24-2.Accelerometercoordinatesystem

Thiscoordinatesystemisdifferentthantheoneusedinlayoutsand2Dgraphics.Inthatcoordinatesystem,theorigin(0,0)isatthetop-leftcorner,andyispositiveinthedirectiondownthescreenfromthere.Itiseasytogetconfusedwhendealingwithcoordinatesystemsindifferentframesofreference,sobecareful.

Wehaven’tyetsaidwhattheaccelerometervaluesmean,sowhatdotheymean?Accelerationismeasuredinmeterspersecondsquared(m/s2).NormalEarthgravityis9.81m/s2,pullingdowntowardthecenteroftheEarth.Fromtheaccelerometer’spointofview,themeasurementofgravityis–9.81.Ifyourdeviceiscompletelyatrest(notmoving)andisonaperfectlyflatsurface,thexandyreadingswillbe0andthezreadingwillbe+9.81.Actually,thevalueswon’tbeexactlythesebecauseofthesensitivityandaccuracyoftheaccelerometer,buttheywillbeclose.Gravityistheonlyforceactingonthedevicewhenthedeviceisatrest,andbecausegravitypullsstraightdown,ifourdeviceisperfectlyflat,itseffectonthexandyaxesiszero.Onthezaxis,theaccelerometerismeasuringtheforceonthedeviceminusgravity.Therefore,0minus–9.81is+9.81,andthat’swhatthezvaluewillbe(a.k.a.values[2]intheSensorEventobject).

Thevaluessenttoyourapplicationbytheaccelerometeralwaysrepresentthesumoftheforcesonthedeviceminusgravity.Ifyouweretotakeyourperfectlyflatdeviceandliftitstraightup,thezvaluewouldincreaseatfirst,becauseyouincreasedtheforceintheup(z)direction.Assoonasyourliftingforcestopped,theoverallforcewouldreturntobeingjustgravity.Ifthedeviceweretobedropped(hypothetically—pleasedon’tdothis),itwouldbeacceleratingtowardtheground,whichwouldzerooutgravitysotheaccelerometerwouldread0force.

Let’stakethedevicefromFigure24-2androtateitupsoitisinportraitmodeandvertical.Thexaxisisthesame,pointinglefttoright.Ouryaxisisnowstraightupanddown,andthezaxisispointingoutofthescreenstraightatus.Theyvaluewillbe+9.81,andbothxandzwillbe0.

Whathappenswhenyourotatethedevicetolandscapemodeandcontinuetoholditvertically,sothescreenisrightinfrontofyourface?Ifyouguessedthatyandzarenow0andxis+9.81,you’dbecorrect.Figure24-3showswhatitmightlooklike.

Figure24-3.Accelerometervaluesinlandscapevertical

Whenthedeviceisnotmoving,orismovingwithaconstantvelocity,theaccelerometersareonlymeasuringgravity.Andineachaxis,thevaluefromtheaccelerometerisgravity’scomponentinthataxis.Therefore,usingsometrigonometry,youcouldfigureouttheanglesandknowhowthedeviceisorientedrelativetogravity’spull.Thatis,youcouldtellifthedevicewereinportraitmodeorinlandscapemodeorinsometiltedmode.Infact,thisisexactlywhatAndroiddoestofigureoutwhichdisplaymodetouse(portraitorlandscape).Note,however,thattheaccelerometersdonotsayhowthedeviceisoriented

withrespecttomagneticnorth.Sowhileyoucouldknowthatthedeviceisbeingheldinlandscapemodevertically,youwouldn’tknowifyouwerefacingeastorwestoranywhereinbetween.That’swherethemagneticfieldsensorwillcomein,whichwewillcoverinalatersection.

AccelerometersandDisplayOrientationAccelerometersinadevicearehardware,andthey’refirmlyattached,andassuchhaveaspecificorientationrelativetothedevicethatdoesnotchangeasthedeviceisturnedthiswayorthat.ThevaluesthattheaccelerometerssendintoAndroidwillchangeofcourseasadeviceismoved,butthecoordinatesystemoftheaccelerometerswillstaythesamerelativetothephysicaldevice.Thecoordinatesystemofthedisplay,however,changesastheusergoesfromportraittolandscapeandbackagain.Infactdependingonwhichwaythescreenisturned,portraitcouldberight-sideup,or180degreesupside-down.Similarly,landscapecouldbeinoneoftwodifferentrotations180degreesapart.

Whenyourapplicationisreadingaccelerometerdataandwantingtoaffecttheuserinterfacecorrectly,yourapplicationmustknowhowmuchrotationofthedisplayhasoccurredtoproperlycompensate.Asyourscreenisreorientedfromportraittolandscape,thescreen’scoordinatesystemhasrotatedwithrespecttothecoordinatesystemoftheaccelerometers.Tohandlethis,yourapplicationmustusethemethodDisplay.getRotation().Thereturnvalueisasimpleintegerbutnottheactualnumberofdegreesofrotation.ThevaluewillbeoneofSurface.ROTATION_0,Surface.ROTATION_90,Surface.ROTATION_180,orSurface.ROTATION_270.Theseareconstantswithvaluesof0,1,2,and3,respectively.Thisreturnvaluetellsyouhowmuchthedisplayhasrotatedfromthe“normal”orientationofthedevice.BecausenotallAndroiddevicesarenormallyinportraitmode,youcannotassumethatportraitisatROTATION_0.

AccelerometersandGravitySofar,we’veonlybrieflytouchedonwhathappenstotheaccelerometervalueswhenthedeviceismoved.Let’sexplorethatfurther.Allforcesactingonthedevicewillbedetectedbytheaccelerometers.Ifyouliftthedevice,theinitialliftingforceispositiveinthezdirection,andyougetazvaluegreaterthan+9.81.Ifyoupushthedeviceonitsleftside,you’llgetaninitialnegativereadinginthexdirection.

Whatyou’dliketobeabletodoisseparateouttheforceofgravityfromtheotherforcesactingonthedevice.There’safairlyeasywaytodothis,andit’scalledalow-passfilter.Forcesotherthangravityactingonthedevicewilldosoinawaythatistypicallynotgradual.Inotherwords,iftheuserisshakingthedevice,theshakingforcesarereflectedintheaccelerometervaluesquickly.Alow-passfilterwillineffectstripouttheshakingforcesandleaveonlythesteadyforce,whichisgravity.Let’suseasampleapplicationtoillustratethisconcept.It’scalledGravityDemo.Listing24-3showstheJavacode.

Listing24-3.MeasuringGravityfromtheAccelerometers

//ThisfileisMainActivity.java

publicclassMainActivityextendsActivityimplementsSensorEventListener{privateSensorManagermgr;privateSensoraccelerometer;privateTextViewtext;privatefloat[]gravity=newfloat[3];privatefloat[]motion=newfloat[3];privatedoubleratio;privatedoublemAngle;privateintcounter=0;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);

mgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);accelerometer=mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);text=(TextView)findViewById(R.id.text);}

@OverrideprotectedvoidonResume(){mgr.registerListener(this,accelerometer,SensorManager.SENSOR_DELAY_UI);super.onResume();}

@OverrideprotectedvoidonPause(){mgr.unregisterListener(this,accelerometer);super.onPause();}

publicvoidonAccuracyChanged(Sensorsensor,intaccuracy){//ignore}

publicvoidonSensorChanged(SensorEventevent){//Usealow-passfiltertogetgravity.//Motioniswhat'sleftoverfor(inti=0;i<3;i++){gravity[i]=(float)(0.1*event.values[i]+0.9*gravity[i]);

motion[i]=event.values[i]-gravity[i];}

//ratioisgravityontheYaxiscomparedtofullgravity//shouldbenomorethan1,nolessthan-1ratio=gravity[1]/SensorManager.GRAVITY_EARTH;if(ratio>1.0)ratio=1.0;if(ratio<-1.0)ratio=-1.0;

//convertradianstodegrees,makenegativeiffacingupmAngle=Math.toDegrees(Math.acos(ratio));if(gravity[2]<0){mAngle=-mAngle;}

//Displayevery10thvalueif(counter++%10==0){Stringmsg=String.format("Rawvalues\nX:%8.4f\nY:%8.4f\nZ:%8.4f\n"+"Gravity\nX:%8.4f\nY:%8.4f\nZ:%8.4f\n"+"Motion\nX:%8.4f\nY:%8.4f\nZ:%8.4f\nAngle:%8.1f",event.values[0],event.values[1],event.values[2],gravity[0],gravity[1],gravity[2],motion[0],motion[1],motion[2],mAngle);text.setText(msg);text.invalidate();counter=1;}}}

TheresultofrunningthisapplicationisadisplaythatlookslikeFigure24-4.Thisscreenshotwastakenasthedevicelayflatonatable.

Figure24-4.Gravity,motion,andanglevalues

MostofthissampleapplicationisthesameastheAccelSensorapplicationfrombefore.ThedifferencesareintheonSensorChanged()method.Insteadofsimplydisplayingthevaluesfromtheeventarray,weattempttokeeptrackofgravityandmotion.Yougetgravitybyusingonlyasmallportionofthenewvaluefromtheeventarray,andalargeportionofthepreviousvalueofthegravityarray.Thetwoportionsusedmustaddupto1.0.Weused0.9and0.1.Youcouldtryothervalues,too,suchas0.8and0.2.Ourgravityarraycannotpossiblychangeasfastastheactualsensorvaluesarechanging.Butthisisclosertoreality.Andthisiswhatalow-passfilterdoes.Theeventarrayvalueswouldonlybechangingifforceswerecausingthedevicetomove,andyoudon’twanttomeasurethoseforcesaspartofgravity.Youonlywanttorecordintoyourgravityarraytheforceofgravityitself.Themathheredoesnotmeanyou’remagicallyrecordingonlygravity,butthevaluesyou’recalculatingaregoingtobealotcloserthantherawvaluesfromtheeventarray.

Noticealsothemotionarrayinthecode.Bytrackingthedifferencebetweentheraweventarrayvaluesandthecalculatedgravityvalues,youarebasicallymeasuringtheactive,non-gravity,forcesonthedeviceinthemotionarray.Ifthevaluesinthemotionarrayarezeroorveryclosetozero,itmeansthedeviceisprobablynotmoving.Thisisusefulinformation.Technically,adevicemovinginaconstantspeedwouldalsohavevaluesinthemotionarrayclosetozero,buttherealityisthatifauserismovingthedevice,themotionvalueswillbesomewhatlargerthanzero.Userscan’tpossiblymoveadeviceataperfectconstantspeed.

Lastly,pleasenoticethatthisexampledoesnotproducenewobjectsthatneedtobegarbagecollected.Itisveryimportantwhendealingwithsensoreventstonotcreatenew

objects;otherwiseyourapplicationwillspendtoomuchtimepausedforgarbagecollectioncycles.

UsingAccelerometerstoMeasuretheDevice’sAngleWewantedtoshowyouonemorethingabouttheaccelerometersbeforewemoveon.Ifwegobacktoourtrigonometrylessons,werememberthatthecosineofanangleistheratioofthenearsideandthehypotenuse.Ifweconsidertheanglebetweentheyaxisandgravityitself,wecouldmeasuretheforceofgravityontheyaxisandtakethearccosinetodeterminetheangle.We’vedonethatinthiscodeaswell,althoughherewehavetodealyetagainwithsomeofthemessinessofsensorsinAndroid.ThereareconstantsinSensorManagerfordifferentgravityconstants,includingEarth’s.Butyouractualmeasuredvaluescouldpossiblyexceedthedefinedconstants.Wewillexplainwhatwemeanbythisnext.

Intheory,yourdeviceatrestwouldmeasureavalueforgravityequaltotheconstantvalue,butthisisrarelythecase.Atrest,theaccelerometersensorisverylikelytogiveusavalueforgravitythatislargerorsmallerthantheconstant.Therefore,ourratiocouldendupgreaterthanone,orlessthannegativeone.Thiswouldmaketheacos()methodcomplain,sowefixtheratiovaluetobenomorethan1andnolessthan–1.Thecorrespondinganglesindegreesrangefrom0to180.That’sfineexceptthatwedon’tgetnegativeanglesfrom0to–180thisway.Togetthenegativeangles,weuseanothervaluefromourgravityarray,whichisthezvalue.Ifthezvalueofgravityisnegative,itmeansthedevice’sfaceisorienteddownward.Forallthosevalueswherethedevicefaceispointeddown,wemakeouranglenegativeaswell,withtheresultbeingthatouranglegoesfrom–180to+180,justaswewouldexpect.

Goaheadandexperimentwiththissampleapplication.Noticethatthevalueoftheangleis90whenthedeviceislaidflat,andit’szero(orclosetoit)whenthedeviceisheldstraightupanddowninfrontofus.Ifwekeeprotatingdownpastflat,wewillseethevalueoftheangleexceed90.Ifwetiltthedeviceupmorefromthe0position,thevalueofanglegoesnegativeuntilwe’reholdingthedeviceaboveourheadsandthevalueoftheangleis–90.Finally,youmayhavenoticedourcounterthatcontrolshowoftenthedisplayisupdated.Becausethesensoreventscancomeratherfrequently,wedecidedtoonlydisplayeverytenthtimewegetvalues.

MagneticFieldSensorsThemagneticfieldsensormeasurestheambientmagneticfieldinthex,y,andzaxes.Thiscoordinatesystemisalignedjustliketheaccelerometers,sox,y,andzareasshowninFigure24-2.Theunitsofthemagneticfieldsensoraremicroteslas(uT).ThissensorcandetecttheEarth’smagneticfieldandthereforetelluswherenorthis.Thissensorisalsoreferredtoasthecompass,andinfactthe<uses-feature>tagusesandroid.hardware.sensor.compassasthenameofthissensor.Becausethissensorissotinyandsensitive,itcanbeaffectedbymagneticfieldsgeneratedbythingsnearthedevice,andeventosomeextenttocomponentswithinthedevice.Thereforetheaccuracyofthemagneticfieldsensormayattimesbesuspect.

We’veincludedasimpleCompassSensorapplicationinthedownloadsectionofthewebsite,sofeelfreetoimportthatandplaywithit.Ifyoubringmetalobjectsclosetothedevicewhilethisapplicationisrunning,youmightnoticethevalueschanginginresponse.Certainlyifyoubringamagnetclosetothedeviceyouwillseethevalueschange.Infact,theGoogleCardboard“device”usesamagnetunderaphysicalbuttonwhichisthendetectedbythephoneasachangeinthemagneticfieldwhenthebuttonispressed.

Youmightbeasking,canIusethecompasssensorasacompasstodetectwherenorthis?Andtheansweris:notbyitself.Whilethecompasssensorcandetectmagneticfieldsaroundthedevice,ifthedeviceisnotbeingheldperfectlyflatinrelationtotheEarth’ssurface,you’dhavenowayofcorrectlyinterpretingthecompasssensorvalues.ButyouhaveaccelerometersthatcantellyoutheorientationofthedevicerelativetotheEarth’ssurface!Therefore,youcancreateacompassfromthecompasssensor,butyou’llneedhelpfromtheaccelerometerstoo.Solet’sseehowtodothat.

UsingAccelerometersandMagneticFieldSensorsTogetherTheSensorManagerprovidessomemethodsthatallowustocombinethecompasssensorandtheaccelerometerstofigureoutorientation.Aswejustdiscussed,youcan’tusejustthecompasssensoralonetodothejob.SoSensorManagerprovidesamethodcalledgetRotationMatrix(),whichtakesthevaluesfromtheaccelerometersandfromthecompassandreturnsamatrixthatcanbeusedtodetermineorientation.

AnotherSensorManagermethod,getOrientation(),takestherotationmatrixfromthepreviousstepandgivesanorientationmatrix.Thevaluesfromtheorientationmatrixtellyouyourdevice’srotationrelativetotheEarth’smagneticnorth,aswellasthedevice’spitchandrollrelativetotheground.

MagneticDeclinationandGeomagneticFieldThere’sanothertopicwewanttocoverwithregardtoorientationanddevices.Thecompasssensorwilltellyouwheremagneticnorthis,butitwon’ttellyouwheretruenorthis(a.k.a.,geographicnorth).Imagineyouarestandingatthemidpointbetweenthemagneticnorthpoleandthegeographicnorthpole.They’dbe180degreesapart.Thefurtherawayyougetfromthetwonorthpoles,thesmallerthisangledifferencebecomes.Theangledifferencebetweenmagneticnorthandtruenorthiscalledmagneticdeclination.Andthevaluecanonlybecomputedrelativetoapointontheplanet’ssurface.Thatis,youhavetoknowwhereyou’restandingtoknowwheregeographicnorthisinrelationtomagneticnorth.Fortunately,Androidhasawaytohelpyouout,andit’stheGeomagneticFieldclass.

InordertoinstantiateanobjectoftheGeomagneticFieldclass,youneedtopassinalatitudeandlongitude.Therefore,inordertogetamagneticdeclinationangle,youneedtoknowwherethepointofreferenceis.Youalsoneedtoknowthetimeatwhichyouwantthevalue.Magneticnorthdriftsovertime.Onceinstantiated,yousimplycallthismethod

togetthedeclinationangle(indegrees):

floatdeclinationAngle=geoMagField.getDeclination();

ThevalueofdeclinationAnglewillbepositiveifmagneticnorthistotheeastofgeographicnorth.

GravitySensorsThissensorisn’taseparatepieceofhardware.It’savirtualsensorbasedontheaccelerometers.Infact,thissensoruseslogicsimilartowhatwedescribedearlierforaccelerometerstoproducethegravitycomponentoftheforcesactingonadevice.Wecannotaccessthislogic,however,sowhateverfactorsandlogicareusedinsidethegravitysensorclassarewhatwemustaccept.It’spossible,though,thatthevirtualsensorwilltakeadvantageofotherhardwaresuchasagyroscopetohelpitcalculategravitymoreaccurately.Thevaluesarrayforthissensorreportsgravityjustliketheaccelerometersensorreportsitsvalues.

LinearAccelerationSensorsSimilartothegravitysensor,thelinearaccelerationsensorisavirtualsensorthatrepresentstheaccelerometerforcesminusgravity.Again,wedidourowncalculationsearlierontheaccelerometersensorvaluestostripoutgravitytogetjusttheselinearaccelerationforcevalues.Thissensormakesthatmoreconvenientforyou.Anditcouldtakeadvantageofotherhardware,suchasagyroscope,tohelpitcalculatelinearaccelerationmoreaccurately.Thevaluesarrayreportslinearaccelerationjustliketheaccelerometersensorreportsitsvalues.

RotationVectorSensorsTherotationvectorsensorrepresentstheorientationofthedeviceinspace,withanglesrelativetotheframeofreferenceofthehardwareaccelerometer(seeFigure24-2).Thissensorreturnsasetofvaluesthatrepresentsthelastthreecomponentsofaunitquaternion.Quaternionsareasubjectthatcouldfillabook,sowewon’tbegoingintothemhere.

Thankfully,GooglehasprovidedafewmethodswithinSensorManagertohelpwiththissensor.ThegetQuaternionFromVector()methodconvertsarotationvectorsensoroutputtoanormalizedquaternion.ThegetRotationMatrixFromVector()methodconvertsarotationvectorsensoroutputtoarotationmatrix,andthatcanbeusedwithgetOrientation().Whenconvertingrotationvectorsensoroutputtoanorientationvector,though,youneedtorealizethatitgoesfrom–180degreesto+180degrees.

TheZIPfileofsampleappsforthischapterincludesaversionofVirtualJaxthatshowstherotationvectorinuse.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch24_Sensors.zip.Thisfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneoftheseZIPfiles.

http://en.wikipedia.org/wiki/Lux:TheWikipediaentryforlux,theunitoflightmeasurement.

www.ngdc.noaa.gov/geomag/faqgeom.shtml:InformationaboutgeomagnetismfromNOAA.

www.youtube.com/watch?v=C7JQ7Rpwn2k:AGoogleTechTalkfromDavidSachsonaccelerometers,gyroscopes,compasses,andAndroiddevelopment.

http://stackoverflow.com/questions/1586658/combine-gyroscope-and-accelerometer-data:Anicepostingonstackoverflow.comthattalksaboutcombininggyroscopeandaccelerometersensordataforuseinapplications.

http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotationTheWikipediapageonquaternionsandhowtheycanbeusedinrepresentingspatialrotation,suchasanAndroiddevice.

SummaryInthischapter,wecoveredthefollowingtopics:

WhatsensorsareinAndroid.

Findingoutwhatsensorsareonadevice.

SpecifyingthesensorsthatarerequiredforanapplicationbeforeitwillbeloadableontoanAndroiddevice.

Determiningthepropertiesofasensoronadevice.

Howtogetsensorevents.

Thefactthateventscomewheneverthesensorvaluechanges,soitisimportanttounderstandtherecouldbealagbeforeyougetyourfirstvalue.

Thedifferentspeedsofupdatesfromasensorandwhentouseeach

one.

ThedetailsofaSensorEventandhowthesecanbeusedforthevarioussensortypes.

Virtualsensors,madeupofdatafromothersensors.TheROTATION_VECTORsensorisoneofthese.

Determiningtheangleofthedeviceusingsensors,andtellingwhichdirectionthedeviceisfacing.

Chapter25

ExploringAndroidPersistenceandContentProvidersThereareanumberofwaysofsavingstateintheAndroidSDK.Someoftheseare1)sharedpreferences,2)internalfiles,3)externalfiles,4)SQLite,5)contentproviders,6)O/Rmappingtools,and7)networkstorageinthecloud.Wewillbrieflyintroduceeachofthesestate-savingoptionsfirstandthencoverindetailmanagingapplicationstateusingSQLiteandcontentproviders.

SavingStateUsingSharedPreferencesWehavecoveredsharedpreferencesinChapter11.Sharedpreferencesarekey/value-basedXMLfilesownedbyyourapplication.Androidhasaframeworkontopofthisgeneralpersistencemechanismtodisplay/update/retrievepreferenceswithoutwritingalotofcode.ThislatteraspectisthemaintopicofChapter11.

Chapter11alsotouchedbrieflyonhowanapplicationcanstoreanytypeofdatausingthesharedpreferenceAPIinXMLfiles.Inthisapproachdataisconvertedtoastringrepresentationfirstandthenstoredinthepreferenceskey/valuestore.Thisapproachcanbeusedtostoreanyarbitrarystateofyourapplicationaslongasitissmalltomediuminsize.

ThesharedpreferenceXMLfilesareinternaltotheapplicationonyourdevice.Thisdataisnotdirectlyavailabletootherapplications.EndusercannotdirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.

Fromsimpletomoderateapplicationpersistenceneeds,youcantakeadvantageofsharedpreferencesbystoringvarioustreesofJavaobjectsdirectlyinasharedpreferencefile.InagivenpreferencefileyoucanhaveakeypointtoaserializedJavaobjecttree.YoucanalsousemultiplepreferencefilesformultipleJavaobjecttrees.WehaveusedJSON/GSONlibraryfromgoogletodothisconversionfromJavaobjectstotheirequivalentJSONstringvaluesquiteeffectively.InthisapproachaJavaobjecttreeisstreamedasaJSONstringusingthegoogleGSONlibrary.Thistreeisthenstoredasavalueinakey/valuepairofapreferencefile.KeepinmindthatGSONandJSONconversionofaJavaobjectmayhavesomelimitations.ReadtheGSON/JSONdocumentationtoseehowcomplexaJavaobjectcangettomakethisapproachwork.Wearefairlyconfidentthatformostdata-basedJavaobjectsthiswillwork.

Listing25-1hassomesamplecodeforhowtosaveaJavatreeusingGSON/JSONandsharedpreferences.

Listing25-1.SavingaJavaObjectTreeUsingJSONinSharedPreferencesXMLFiles

//ImplementationofstoreJSONforstoringanyobjectpublicvoidstoreJSON(Contextcontext,ObjectanyObject){

//GetaGSONinstanceGsongson=newGson();

//ConvertJavaobjecttoaJSONstringStringjsonString=gson.toJson(anyObject);

//SeeChapter11formoredetailsonhowtogetasharedpreferencesreferenceStringfilename="somefilename.xml";intmode=Context.MODE_PRIVATE;SharedPreferencessp=context.getSharedPreferences(filename,mode);

//SavetheJSONstringinthesharedpreferencesSharedPreferences.Editorspe=sp.edit();spe.putString("json",jsonString);spe.commit();}//Thiscodecanthenbeusedbyaclientlikethis://Createanydataobjectwithreasonablecomplexity//Ex:MainObjectmo=MainObject.createTestMainObject();//YoucanthencallstoreJSON(some-activity,mo)below

Listing25-2showssomesamplecodeforhowtoretrieveaJavatreeusingGSON/JSONandsharedpreferences.

Listing25-2.ReadingaJavaObjectTreeUsingJSONfromSharedPreferencesXMLFiles

publicObjectretrieveJSON(Contextcontext,Stringfilename,ClassclassRef){intmode=Context.MODE_PRIVATE;SharedPreferencessp=context.getSharedPreferences(filename,mode);StringjsonString=sp.getString("json",null);if(jsonString==null){thrownewRuntimeException("Notabletoreadthepreference");}Gsongson=newGson();returngson.fromJson(jsonString,classRef);}

//YoucanthendothisintheclientcodeMainObjectmo

=(MainObject)retrieveJSON(context,"somefilename.xml",MainObject.class);StringcompareResult=MainObject.checkTestMainObject(mo);if(compareResult!=null){thrownewRuntimeException("Somethingiswrong.Objectsdon'tmatch");}

ThiscoderequiresthatyouhavetheGSONJavalibraryaddedtoyourproject.ThisGSON-basedapproachiscoveredindetailinourcompanionbook,ExpertAndroidfromApress.Thisisalsobrieflydocumentedonlineathttp://androidbook.com/item/4438.

SavingStateUsingInternalFilesInAndroid,youcanalsouseinternalfilestostorethestateofyourapplication.Theseinternalfilesareinternaltotheapplicationonyourdevice.Thisdataisnotdirectlyavailabletootherapplications.EndusercannotdirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.

Listing25-3showssamplecodeforhowtosaveaJavatreeusingGSON/JSONandinternalfiles.

Listing25-3.Reading/WritingJSONStringsfrom/toanAndroidInternalFile

privateObjectreadFromInternalFile(ContextappContext,Stringfilename,ClassclassRef)throwsException{FileInputStreamfis=null;try{fis=appContext.openFileInput(filename);//ReadthefollowingstringfromthefilestreamfisStringjsonString;

Gsongson=newGson();returngson.fromJson(jsonString,classRef);}finally{//writecodetocloseStreamSilently(fis);}}privatevoidsaveToInternalFile(ContextappContext,Stringfilename,ObjectanyObject){Gsongson=newGson();StringjsonString=gson.toJson(anyObject);

FileOutputStreamfos=null;

try{fos=appContext.openFileOutput(filename,Context.MODE_PRIVATE);fos.write(jsonString.getBytes());}finally{//closeStreamSilently(fos);}}

ThisapproachbasedoninternalfilesandGSONiscoveredindetailinourcompanionbook,ExpertAndroidfromApress(http://www.apress.com/9781430249504).Thisisalsobrieflydocumentedonlineathttp://androidbook.com/item/4439.

SavingStateUsingExternalFilesInAndroid,externalfilesarestoredeitherontheSDcardoronthedevice.Thesebecomepublicfilesthatotherappsincludingtheusercouldseeandreadoutsidethecontextofyourapplication.Formanyappsthatwanttomanagetheirinternalstatetheseexternalfileswillunnecessarilypollutethepublicspace.

BecausethedatayouwouldrepresentasJSONistypicallyveryspecifictoyourapplication,itdoesn’tmakesensetomakethisavailableasexternalstorage,whichistypicallyusedformusicfiles,videofiles,orfilesthatarecommonlyinaformatthatisunderstandablebyotherapplications.

BecauseexternalstoragesuchasanSDcardcanbeinvariousstates(available,notmounted,full,etc.),itishardertoprogramthisforsimpleappswhenthedataissmallenough.Sowecouldnotmakeagoodcasefornowthattheapplicationstatebemaintainedonexternalstorage.

AhybridapproachmaybemeaningfuliftheapplicationrequiresmusicandphotosandthosecangoontheexternalstoragewhilekeepingthecorestatedatainJSONandinternal.

Theandroid.os.Environmentclassandtheandroid.content.Contextclasshaveanumberofmethodstoreadandwritetoexternalfilesanddirectories.Wehavenotincludedcodeexamplesbecausetheapproachisverysimilartointernalfilesonceyougetaccesstothesefilesthroughtheandorid.content.Context.

SavingStateUsingSQLiteAndroidappscanuseSQLitedatabasestostoretheirstate.SQLiteiswellintegratedintothefabricofAndroid.Ifyouwanttostoretheinternalstateofyourapplicationrobustlythenthisisprobablythebestapproach.However,workingwithanyrelationaldatabaseincludingSQLitehasalotofnuances.WewillcovertheessentialsandnuancesofusingSQLiteonAndroidalittlelaterinthechapter.

SavingStateUsingO/RMappingLibrariesO/RmappingstandsforObject-to-RelationalMapping.AkeydifficultywithstoringstateinarelationaldatabasefromaprogramminglanguagelikeJavaisthemismatchbetweenJavaobjectstructuresandrelationalstructuresofthedatabase.Oneneedstomapbetweenthenames,types,andrelationshipsoffieldsastheyareintheJavaspaceandtheirequivalentsinthedatabasespace.Thismappingiserrorprone.YouwillseethiswhenwecovertheSQLiteindetaillater.

ThereisaneedforsimplifyingthismappingofdatabetweenJavaandSQL.ThisspaceintheindustryiscalledO/Rmapping.AfewtoolsarenowavailabletosolvethisinAndroid.ItisbeyondthescopeofthisbooktocovertheessentialsoftheseO/Rmappingtools.Butwewillnameacoupleofthesetoolsandgivetheironlinereferencesnow.

TwokeytoolsinthisspaceareGreenDAO(http://greendao-orm.com/)andOrmLite(http://ormlite.com/).Therearemoreappearingeveryyear.Socheckoftentoseeifthenewonesarefasteroreasier.GreenDAOusesacodegenerationapproachbasedonschemadefinitions.ItissaidtobethreetofourtimesfasterthanOrmLite.OrmLitefusestheschemadefinitionwithJavaclassesthroughannotations.Thelatterapproachiseasierprogrammatically.AlsoOrmLiteworksthesameonanyJavaplatform.However,possiblyduetoreflectionusedatruntime,itcanbeslower,butIsuspectisfastenoughformostapplications.

WepredictthatusingoneoftheseO/Rmappinglibrariesisakeyneedtogetyourappsfastertothemarket.WerecommendthatyouisolatethepersistenceservicesandstartwithOrmLiteandthenmovetoGreenDAOifyourappgainsenoughtractionorformovingtoproductionfromyourprototype.

SavingStateUsingContentProvidersAndroidprovidesahigher-levelabstractionontopofdatastoresbasedonURIs.UsingthisapproachanyapplicationcanreadorstoredatausingRESTlikeURIs.ThisabstractionalsoallowsapplicationstosharetheirdatathroughAPIsbasedonURIstrings.InthisapproachsubmittingaURIwillgivebackacollectionofrowsandcolumnsinadatabasecursor.AURIcanalsotakeasetofkey/valuepairsandpersisttheminatargetdatabaseifpermissionsaregranted.Thisisageneral-purposemechanismforinteroperabilityofdatabetweenAndroidapplications.Wewillcoverthisingreaterdetaillaterinthechapter.Thisisapreferredmechanismifyourapplicationhasdatathatisvaluabletobeshared,created,ormanipulatedbyotherapplications.Forexamplemanyapplicationsthatdealwithnotes,documents,audio,orvideoimplementtheirdataascontentproviders.ThisisalsothecasewithmostofAndroid’scoredata-relatedservices.

SavingStateUsingNetworkStorageNetworkstoragecomesintoplaywhenthedatacreatedorusedbyanapplicationneedstobesharedviaanetworkbyotherusersoneitherthesameplatformordifferentplatformslikeinacollaborativeapplication.Thisback-endservicefacilityutilizedbymobile

applicationisbeingcalledMBaaS(MobileBack-endAsAService).Parse.comisanexampleofaMBAASthatprovidesback-endservicessuchasusermanagement,userlogins,security,social,commonnetworkstorage,serversidebusinesslogic,andnotifications.

Androidalsonativelyusesaconceptcalledsyncadapterstotransferdatabetweenthedeviceandnetworkservers.Youcanreadmoreonsyncadaptersathttp://developer.android.com/training/sync-adapters/index.html.Thisisaframeworkthatusesasynchronouscallbackstooptimizetransferofarbitraryamountsofdataefficientlybyschedulingandexecutingitatthemostopportunemoment.Theframeworksweatsthedetailanddevelopersjustprovidethetransfercode.

ThatconcludestheoverviewofvariouswaystosavestateforAndroidmobileapplications.Wewillcovernowtwoofthoseapproachesindetail:SQLiteandcontentproviders.WewillstartwiththeAndroidSQLiteAPI.

StoringDataDirectlyUsingSQLiteInthissectionwewillexploreindetailhowtouseSQLiteeffectivelytomanageAndroidapplicationstate.YouwillunderstandtheextentofSQLitesupportinAndroid.Wewillshowyoutheessentialcodesnippets.WewillshowyoubestpracticesforusingSQLiteonAndroid.WewillshowyouhowbesttoloadDDLstocreateyourdatabase.Wewillshowyouacleanerarchitecturalpatterntoabstractpersistenceservices.Wewillshowhowtoapplytransactionsthroughdynamicproxies.ThissectionisarobusttreatmentofusingSQLiteonAndroid.Wealsohaveasampleprogramthatyoucandownloadtoseethecompleteworkingimplementation.Let’sstartwithaquickoverviewofSQLitepackagesandclassesinAndroid.

SummarizingKeySQLitePackagesandClassesAndroidsupportsSQLitethroughitsJavapackageandroid.database.sqlite.SomeofthekeyclassesyouwillneedtounderstandforeffectivelyusingtheAndroidSQLiteAPIarelistedinListing25-4.Notethatsomeoftheclassesareoutsidetheandroid.database.sqlitepackage.

Listing25-4.KeySQLiteJavaClassesintheAndroidSDK

android.database.sqlite.SQLiteDatabaseandroid.database.sqlite.SQLiteCursorandroid.database.sqlite.SQLiteQueryBuilderandroid.content.ContentValuesandroid.database.Cursorandroid.database.SQLExceptionandroid.database.sqlite.SQLiteOpenHelper

Let’stalkabouteachofthesepackagesandclassesbriefly.

SQLiteDatabase:SQLiteDatabaseisaJavaclassthatrepresentsthedatabaseusuallyreferringtoa“.db”fileonthefilesystem.Usingthisobjectyoucanquery,insert,update,ordeleteforagiventableinthatdatabase.YoucanalsoexecuteasinglearbitrarySQLstatement.Youcanapplytransactions.YoucanalsousethisobjecttodefinetablesthroughDDLs(DataDefinitionLanguage).DDLsarestatementsthatletyoucreatedatabaseentitiessuchastables,views,indexes,etc.Typicallythereisasingleinstanceofthisobjectinyourapplicationrepresentingyourdatabase.

SQLiteCursor:ThisJavaclassrepresentsacollectionofrowsthatarereturnedfromanSQLiteDatabase.Italsoimplementstheandroid.database.Cursorinterface.Thisobjecthasmethodstonavigatetherowsoneatatimelikeaforwarddatabasecursorandretrievingtherowsonlyasneeded.Thisobjectcanalsojumpforwardorbackwardifneededlikearandomcursorbyimplementingwindowingqualities.Thisisalsotheobjectyouwillusetoreadthecolumnvaluesforanycurrentrow.

SQLiteQueryBuilder:ThisisahelperJavaclasstoconstructanSQLitequerystringbyincrementallyspecifyingtablenames,columnnames,whereclause,etc.,asseparatefields.ThisclasshasanumberofsetmethodstograduallybuildupthequeryasopposedtospecifyingtheentireSQLQueryasastring.YoucanalsousethequerymethodsdirectlyontheSQLiteDatabaseclassifyourqueryissimple.

ContentValues:AJavaobjectofthisclassholdsasetofkey/valuepairsthatareusedbyanumberofSQLiteclassestoinsertorupdatearowofdata.

SQLException:MostAndroidSQLitedatabaseAPIsthrowthisexceptionwhenthereareerrors.

SQLiteOpenHelper:ThishelperJavaobjectprovidesaccesstoanSQLiteDatabasebyexaminingafewthings:givenafilenameofthedatabase,thisobjectcheckstoseeifthatdatabaseisalreadyinstalledandavailable.Ifitisavailableitcheckstoseeiftheversionisthesame.IftheversionisalsothesameitprovidesareferencetotheSQLiteDatabaserepresentingthatdatabase.Iftheversionisdifferentitprovidesacallbacktomigratethedatabasepriortoprovidingavalidreferencetothedatabase.Ifthedatabasefiledoesn’texistthenitprovidesacallbacktocreateandpopulatethedatabase.Youwillextendthisbaseclassandprovideimplementationstothesevariouscallbacks.Youwillseethisshortlyintheprovidedcodesnippets.

ThatisaquicksummaryofthekeyclassesyouusetosavestateofyourapplicationinanSQLitedatabase.LetusnowturntokeyconceptsinusingSQLiteformanagingapplicationstate.Let’sstartwithcreatingadatabase.

CreatinganSQLiteDatabaseCreationofadatabaseinAndroidiscontrolledthroughtheSQLiteOpenHelperclass.ForeachdatabaseinyourapplicationyouwillhaveaJavadatabaseobjectthatisaninstanceofthisclass.ThisSQLiteOpenHelperobjecthasapairofgetmethodstogetareferencetotheread-optimized(configuredfor)orwrite-optimized(configuredfor)SQLiteDatabaseobject.CreatingorgettingaccesstoyourSQLitedatabaseobject

involvesthefollowing:

1. ExtendingSQLiteOpenHelperandsupplyingthedatabasenameandversiontotheconstructorofthisderivedclasssothatthosevaluescanbepassedtothebaseclass

2. OverridingtheonCreate(),onUpgrade(),andonDowngrade()methodsfromSQLiteOpenHelper.YougetacalltoonCreate()ifthisdatabaseisnotthere.YougetacalltoonUpgrade()iftheversionofthedatabaseisnewer,andacalltoonDowngrade()iftheversionofthedatabaseisolderfromtheonethatisonthedevice.YouwilluseexecuteDDLstatementsinthesemethodstocreateoradjustyourdatabase.Ifyourdatabaseisnotneworhasthesameversion,thenneitherofthesecallbackswillbeinvoked.

3. Haveasinglestaticreferencetothisderivedobject.Callgetmethodsonthisobjecttogetareferencetoacopyofreadableorwritabledatabase.UsethesedatabasereferencestoperformCRUDoperationsandtransactions.

Listing25-5isacodesnippetthatdemonstrateshowthesestepsareimplementedincreatingadatabasecalled“booksqlite.db”,adatabasetoholdasingletableofbooksandtheirdetail.

Listing25-5.UsingSQLiteOpenHelper

//Filereferenceinproject:DirectAccessBookDBHelper.Java/***AcompleteexampleofSQLiteOpenHelperdemonstrating*1.Howtocreateadatabases*2.Howtomigrateadatabase*3.Howtoholdastaticreference*4.Howtogiveoutreadandwritedatabasereferences**ThisclassalsocanactasaDatabaseContext.IFactorytoproducereadandwrite*databasereferences.Thisaspectisnotcriticaltounderstandingbutincluded*foradvancedreadersandforsomemateriallaterinthechapter.*/publicclassDirectAccessBookDBHelperextendsSQLiteOpenHelper

implementsDatabaseContext.IFactory{//thereisoneandonlyoneofthesedatabasehelpers//forthisdatabaseforthisentireapplicationpublicstaticDirectAccessBookDBHelperm_self=new

DirectAccessBookDBHelper(MyApplication.m_appContext);

//NameofthedatabaseonthedeviceprivatestaticfinalStringDATABASE_NAME="bookSQLite.db";

//NameoftheDDLfileyouwanttoloadwhilecreatingadatabaseprivatestaticfinalStringCREATE_DATABASE_FILENAME="create-book-db.sql";

//CurrentversionnumberofthedatabasefortheApptoworkprivatestaticfinalintDATABASE_VERSION=1;

//JustaloggingtagprivatestaticfinalStringTAG="DirectAccessBookDBHelper";

//Passthedatabasenameandversiontothebaseclass//Thisisanonpublicconstructor//Clientscanjustusem_selfandnotconstructthisobjectatalldirectlyDirectAccessBookDBHelper(Contextcontext){

super(context,DATABASE_NAME,null,DATABASE_VERSION);//Initializeanythingelseinyoursystemthatmayneeda//referencetothisobject.//Example:DatabaseContext.initialize(this);}@OverridepublicvoidonCreate(SQLiteDatabasedb){

try{//Nodatabaseexists.LoadDDLfromafileintheassetsdirectoryloadSQLFrom(this.CREATE_DATABASE_FILENAME,db);}catch(Throwablet){//ProblemcreatingdatabasethrownewRuntimeException(t);}}//AfunctiontoloadoneSQLstatementatatimeusingexecSQLmethodprivatevoidloadSQLFrom(StringassetFilename,SQLiteDatabasedb){List<String>statements

=getDDLStatementsFrom(assetFilename);for(Stringstmt:statements){Log.d(TAG,"ExecutingStatement:"+stmt);db.execSQL(stmt);

}}//Optimizethisfunctionforrobustness.//Fornowitassumestherearenocommentsinthefile//thestatementsareseparatedbyasemicolonprivateList<String>getDDLStatementsFrom(StringassetFilename){ArrayList<String>l=newArrayList<String>();Strings=getStringFromAssetFile(assetFilename);for(Stringstmt:s.split(";")){//Addthestmtifitisavalidstatementif(isValid(stmt)){l.add(stmt);}}returnl;}privatebooleanisValid(Strings){//writelogicheretoseeifitisnull,emptyetc.returntrue;//fornow}@OverridepublicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,int

newVersion){

//UseoldandnewversionnumberstorunDDLstatements//toupgradethedatabase}//Usingyourspecificapplicationobjecttoremembertheapplicationcontext//ThenusingthatapplicationcontexttoreadassetsprivateStringgetStringFromAssetFile(Stringfilename){Contextctx=MyApplication.m_appContext;if(ctx==null){thrownewRuntimeException("Sorryyourappcontextisnull");}try{AssetManageram=ctx.getAssets();InputStreamis=am.open(filename);Strings=convertStreamToString(is);is.close();

returns;}catch(IOExceptionx){thrownewRuntimeException("Sorrynotabletoreadfilename:"+filename,x);}}//Optimizelater.ThismaynotbeanefficientreadprivateStringconvertStreamToString(InputStreamis)throwsIOException{ByteArrayOutputStreambaos=newByteArrayOutputStream();inti=is.read();while(i!=-1){baos.write(i);i=is.read();}returnbaos.toString();}//Herearesomeexamplesofhowtogetaccesstoreadableand//writabledatabases.Thesemethodswillmakesenseoncewegetthroughthe//thetransactionsappliedthroughdynamicproxies/*publicReadDatabaseContextcreateReadableDatabase(){returnnewReadDatabaseContext(this.getReadableDatabase());}publicWriteDatabaseContextcreateWritableDatabase(){returnnewWriteDatabaseContext(this.getWritableDatabase());}*/}//eof-classDatabaseHelper//HereisthecodeforMyApplicationtorememberthecontextpublicclassMyApplicationextendsApplication{

publicfinalstaticStringtag="MyApplication";publicstaticvolatileContextm_appContext=null;

@OverridepublicvoidonCreate(){super.onCreate();MyApplication.m_appContext=this.getApplicationContext();}

}//assets/create-book-db.sql

CREATETABLEt_books(idINTEGERPRIMARYKEY,nameTEXT,isbnTEXT,authorTEXT,created_onINTEGER,created_byTEXT,last_updated_onINTEGER,last_updated_byTEXT);

DefiningaDatabaseThroughDDLsInListing25-5,theDirectAccessBookDBHelperisaderivedclassofSQLiteOpenHelperthatallowsustoexamineanexistingdatabaseandseeifitneedstobecreatedorjustmigratedbasedonitsversion.

ThemethodonCreate()iscalledonlyifthisdatabasedoesnotexistonthedevice.WithouttheSQLiteOpenHelperwewouldhavehadtoexaminethephysicallocationofthisfileandseeifitexists.InotherwordsSQLiteOpenHelperisreallyathinwrapperthatissavingusanumberof“if-else”clausestoexaminethedatabaseanddothenecessaryinitialization:beitcreatingitormigratingit.

AnumberofexamplesforcreatinganAndroiddatabaseontheInternetuseembeddedDDLstringsinJavacodetocreatethetablesneeded.AsDDLstatements,stringsinJavacodearedifficulttoreadanderrorprone.Abetterapproachistoputthesedatabasecreationscriptsinatextfileintheassetsdirectory.SamplecodeinListing25-5demonstrateshowtoreadatextfilefromanassetsdirectoryofyourapplicationandusetheexecSQL()functionavailableontheSQLiteDatabasetoinitializethedatabase.

AlimitationofexecSQL()isitcanexecuteonlyoneSQLstatementatatime.ThatiswhythecodeinListing25-5readsthescriptfileandparsesitintoaseriesofstatementsusingasimplesyntax.YoumaywanttoscourtheInternettoseebetterparsingutilitiestoallowabetterscriptfilesupport.Anotheralternative,ifitworksforyourcase,istohaveaschemaclasswhosesolepurposeiscontainstaticpublicstringsforyourDDLasitalleviatestheneedforparsingfiles.WehavelinkstosomeoftheseJava-basedlibrariesintheonlinereferencesprovidedattheendofthischapter.Especially,Java-basedtoolsusingANTLRhavealotofpromiseforcomplexdatabasesetups.

TheonCreate()functionalsowrapsitsexecutioninatransactionsothattheexecuteddatabaseisconsistent.

Ifyouhavealotofscriptsitisalsopossibletocreatethedatabaseentirelyandkeepitintheassetsfolder.Duringdeploymentifthedatabasedoesn’texistyoucanjustcopythefiletoitstargetlocation.

MigratingaDatabaseAsstatedtheSQLiteOpenHelperrecognizesversionnumbersandappropriatelycallstheonUpgrade()methodtoupgradethedatabase.Herealsoyoumaywanttokeepseriesofscriptsintheassetsfolderthatcanalterthedatabaseappropriatelydependingon

thedifferencesintheversionnumbers.Keepinmindthattheversionnumberonthedevicemaybesmallerorlargerthanyourtargetversion.Soyoumayneedasetofscriptsthatareuniquetoeachconversionsequence:GoingfromV1toV3orfromV2toV3orV3toV1.Goingbackwardsmayrequireeitherwarningsordynamicdownloadingofserver-sideconversionstoanolderversion,asthesourcecodeofanolderversionoftheappisunlikelytohavetheneededutilitiestostepdownfromafutureversion.

InsertingRowsAtitscore,insertingarowwithitscolumnvaluesintoSQLiteDatabaseismerelycallingtheinsert-relatedmethodsontheSQLiteDatabaseobject.PseudocodethatexplainsthisisshowninListing25-6.

Listing25-6.BasicsofInsertingaRowUsingSQLiteDatabase

//Getareferencetothedatabaseobject//DependingontheframeworkyouhavetherecouldbemanywaysofdoingthisSQLiteDatatabasedb=DirectAccessBookDBHelper.m_self.getReadableDatabase();Stringtablename;//whichtableyouwanttoinserttherowinto

//populateastructurewiththeneededcolumnsandtheirvaluesContentValuescv=newContentValues();cv.put(columnName1,column1Value);//etc.

//Acolumnthatcouldbenullif'cv'isemptyifanemptyrowisneeded//ProvidenullifthatbehaviorisnotneededStringnullColumnName=null;

//InserttherowlongrowId=db.insertOrThrow(tablename,nullColumnName,cv);

Thiscodeisreallysimple.InsertinganyJavaobjectusingthiscodeismerelyreadingitsattributesandputtingthosevaluesintotheContentValuesdatasetandjustinsert.AsfarasAndroid’sSQLiteinsertcapabilitiesareconcerned,thatisallyouneedtoknow.

HowbesttostructuretogettoyourJavaobjectsandhowtoconvertthosevaluesintothecontentvaluesdependsonyourframework.Thisisatediousprocesstodocorrectly.Butagain,thisdetailisnotessentialforthebasicunderstandingofinsert.Youwillneedthislevelofrigorformostofyourapplications.Youcanskipthisifyouthinkthisiscomplicated,butweareincludingithereaswefeelyouwillneedthislevelofrigorformostofyourapplications.

Sogettingtherightcolumnnamesandvaluesforinsertingrowsrequiressomeworkand

youtypicallyneedthefollowing(irrespectiveoftheframeworkyouuse):

1. AJavaobjectthattypicallyrepresentstherowinadatabase,forexample,aBookobject

2. Atablenametoholdasetofbooks

3. StringnamesforthecolumnsavailableintheBookstable

4. Finally,callingtheinsertmethodtopersisttheBookobjectasarowintheBookstable

Wewillgivecodesnippets(someinpseudocodefashion)foreachoftheseneeds.Foractualcodeyoucandownloadtheprojectforthischapter.HereareacoupleofclassesinListing25-7thatrepresentaBookobjectinJavacode.

Listing25-7.EnsuringMinimalDependencyBetweenDomainObjectsandPersistence

//Filereferenceinproject:BaseEntity.JavapublicclassBaseEntity{

privateintid;//databaseidentifier

privateStringownedAccount=null;//Multi-tenantifneededprivateStringcreatedBy;privateDatecreatedOn;privateStringlastUpdatedBy;privateDatelastUpdatedOn;

publicBaseEntity(StringownedAccount,StringcreatedBy,DatecreatedOn,StringlastUpdatedBy,DatelastUpdatedOn,intid){super();this.ownedAccount=ownedAccount;this.createdBy=createdBy;this.createdOn=createdOn;this.lastUpdatedBy=lastUpdatedBy;this.lastUpdatedOn=lastUpdatedOn;this.id=id;}//ForpersistencepublicBaseEntity(){}

//Usualgeneratedget/setmethods//eliminatedhereforspace.Seethedownloads}//Filereferenceinproject:Book.JavapublicclassBookextendsBaseEntity

{//Keydatafields

//*************************************privateStringname;privateStringauthor;privateStringisbn;//*************************************

publicBook(StringownedAccount,StringcreatedBy,DatecreatedOn,StringlastUpdatedBy,DatelastUpdatedOn,Stringname,Stringauthor,Stringisbn){super(ownedAccount,createdBy,createdOn,lastUpdatedBy,lastUpdatedOn,-1);this.name=name;this.author=author;this.isbn=isbn;}//TohelpwithpersistencepublicBook(){}//Generatedmethodsgetandsetmethods…//....//Thefollowingmethodisherefortestingpurposes//andalsotoseehowabookobjectistypicallycreatedpublicstaticBookcreateAMockBook(){

StringownedAccount="Account1";StringcreatedBy="satya";DatecreatedOn=Calendar.getInstance().getTime();StringlastUpdatedBy="satya";DatelastUpdatedOn=Calendar.getInstance().getTime();

//SeehowmanybooksIhaveandincrementitbyone//Thefollowingmethodreturnsacollectionofbooksinthedatabase//Thisisnotessentialforyourunderstandinghere//YouwillseethisclarifiedwhenyoureadthesectionoftransactionsList<Book>books=Services.PersistenceServices.bookps.getAllBooks();inti=books.size();Stringname=String.format("Book%s",i);Stringauthor="satya";Stringisbn="isbn-12344-"+i;

returnnewBook(ownedAccount,createdBy,createdOn,lastUpdatedBy,lastUpdatedOn,name,author,isbn);}

}

ThislistinghastwoJavaclasses:aBaseEntityandaBookthatextendstheBaseEntity.ObjectsthatlooklikeaBookinListing25-7arecalleddomainobjects.ThesearepureJavaobjectsthatcanmovearoundintheJavaspaceofyourprogramwithoutbeingburdenedbytheirbehaviorrelatingtopersistence.However,whocreatedtheseobjects,whentheywerecreated,andsuchattributesareencapsulatedintheBaseEntitysothatalldomainobjectshavethisbasicinformation.

BecausetheSQLitedatabasemethodsrequireexplicitcolumnnamesfortheseobjectsthataspectisdefinedinaseparatesetofclassesthatdescribethemetadatafortheseobjects.ThesesupportingclassesaregiveninListing25-8.

Listing25-8.DefiningMetadataforDomainObjects

//Filereferenceinproject:BaseEntitySQLiteSQLiteMetaData.JavapublicclassBaseEntitySQLiteSQLiteMetaData{

staticpublicfinalStringOWNED_ACCOUNT_COLNAME="owned_account";staticpublicfinalStringCREATED_BY_COLNAME="created_by";staticpublicfinalStringCREATED_ON_COLNAME="created_on";staticpublicfinalStringLAST_UPDATED_ON="last_updated_on";staticpublicfinalStringLAST_UPDATED_BY="last_updated_by";staticpublicfinalStringID_COLNAME="id";}//Filereferenceinproject:BookSQLiteSQLiteMetaData.JavapublicclassBookSQLiteSQLiteMetaDataextends

BaseEntitySQLiteSQLiteMetaData{

staticpublicfinalStringTABLE_NAME="t_books";staticpublicfinalStringNAME="name";staticpublicfinalStringAUTHOR="author";staticpublicfinalStringISBN="isbn";}

ThesetwoclassesparalleltheirrespectiveBaseEntityandBookobjectclasses.Youhavetopayattentionthatthecolumnnamesmatchtothoseinthedatabase.Sothisneedisfundamentallyerrorprone.UnlessyouuseanO/Rmappinglibraryandcraftoneofyourown,thisissuewillremainandyouhavetotestwell.ItisdefiningtheseclassesexplicitlybytheprogrammerthatiseliminatedintheO/Rmappingtoolsthatwediscussedearlier.

NowthatwehaveaJavaclasstorepresentabookanditsmetadatadefinition,thattellsusthetablenameandthefieldswecanproceedtowritetheJavacodetosaveabookobjectinthedatabase,asshowninListing25-9(notethatthisisstillpseudocodeandusethedownloadtoseeanymissingdetails).

Listing25-9.UsingAndroidSQLiteAPIstoInsertaRow

//Filereferenceinproject:BookPSSQLite.JavaprivatelongcreateBook(Bookbook){//Getaccesstoareaddatabase

SQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();

//Fillfieldsfromthebookobjectintothecontentvalues

ContentValuesbcv=newContentValues();//....fillotherfieldsexamplebcv.put(BookSQLiteSQLiteMetaData.NAME,book.getName());bcv.put(BookSQLiteSQLiteMetaData.ISBN,book.getIsbn());bcv.put(BookSQLiteSQLiteMetaData.AUTHOR,book.getAuthor());//....fillotherfields

//ifbcvisanemptyset,thenanemptyrowcanpossiblybeinserted.//Itisnotthecaseforourbooktable.Ifitwerethough,theemptybcv//willresultinaninsertstatementwithnocolumnnamesinit.//AtleastonecolumnnameisneededbySQLinsertsyntax.//Itisoneofthesecolumnnamesthatgoesbelow.ForusthisisnotcasesoanullStringnullColumnName=null;

longrowId=db.insertOrThrow(BookSQLiteSQLiteMetaData.TABLE_NAME,

nullColumnName,

bcv);

returnrowId;}

ThelogicinListing25-9isquitesimple.GetareferencetoaBookobjectwewanttosave.CopythefieldvaluesfromthebookintoaContentValueskey/valuepairobject.Usemetadataclassestodefinethefieldnamescorrectly.Usethefilled-inContentValuesobjectandcalltheinsertmethod.Ifwedon’tdoanything,theinsertisencapsulatedinanautocommit.Wewilltalkabouthowtodotransactionsshortly,asthetheoryofitisabitinvolvedalthoughthecodeisquitesimpletowrite.TheinsertmethodreturnsthenewlyinsertedprimarykeyIDforthistable.ThisconventionofreturningtheprimarykeyofthetablecomesfromtheunderlyingSQLiteproductdocumentationandisnotAndroidspecific.

ThenullColumnNameisrelatedtothesyntaxoftheSQLinsertstatement.Iftherowhastencolumnsbutonlytwocolumnsandtheirnon-nullvaluesareindicated,thenanewrowisinsertedwiththosetwocolumns,anditisexpectedthattheremainingeightcolumnswillallownulls.Ifyouwantarowwitheverycolumnasnullitispossibleto

issueaninsertstatementwithnocolumnnamesatall,matchingtheemptycontentvaluesset.However,aninsertstatementwithnocolumnnamesisnotallowed.SothisparameternullColumnNamecancontainoneofthecolumnnamesthatcouldbenullsothattheinsertstatementsyntaxrequirementissatisfied.Therestofthecolumnswillbemadenullbythedatabaseinternallywhenthisrowisinserted.Usuallythiscolumnnameispassedinasnullbecauseitisrarethatwewanttoinsertarowwhereeverycolumnisemptyornull.

UpdatingRowsListing25-10isasamplepseudocodesnippet(forfullcodeseethedownloadproject)toshowhowtoupdatearowinthedatabase.NoticehowBookobjectandBookSQLiteMetaDataclassesareusedtominimizeerrorsinspecifyingtablenamesandcolumnnames.Theapproachissimilartotheinsertmethod.

Listing25-10.AndroidSQLiteAPItoUpdateaRecord

//Filereferenceinproject:BookPSSQLite.JavapublicvoidupdateBook(Bookbook){

if(book.getId()<0){thrownewSQLException("Bookidislessthan0");}//GetaccesstoareaddatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();

//FillfieldsfromthebookobjectintothecontentvaluesContentValuesbcv=newContentValues();//....fillotherfieldsbcv.put(BookSQLiteSQLiteMetaData.NAME,book.getName());bcv.put(BookSQLiteSQLiteMetaData.ISBN,book.getIsbn());bcv.put(BookSQLiteSQLiteMetaData.AUTHOR,book.getAuthor());//....fillotherfields

//YoucandothisStringwhereClause=String.format("%s=%s",BookSQLiteSQLiteMetaData.ID_COLNAME,book.getId());StringwhereClauseArgs=null;//Orthenext4lines(thisispreferred)StringwhereClause2=BookSQLiteSQLiteMetaData.ID_COLNAME+"=?";String[]whereClause2Args=newString[1];whereClause2Args[1]=Integer.toString(book.getId());

intcount=db.update(BookSQLiteSQLiteMetaData.TABLE_NAME,bcv,whereClause2,whereClause2Args);if(count==0){

thrownewSQLException(String.format("Failedtoupdatebookforbookid:%s",book.getId()));}}

DeletingRowsListing25-11isanexampleofhowtodeletearowfromthedatabase.

Listing25-11.AndroidSQLiteAPItoDeleteaRecord

//Filereferenceinproject:BookPSSQLite.JavapublicvoiddeleteBook(intbookid){

//GetaccesstoawritabledatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();

Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;StringwhereClause=String.format("%s=%s;",BookSQLiteSQLiteMetaData.ID_COLNAME,bookid);String[]whereClauseargs=null;inti=db.delete(tname,whereClause,whereClauseargs);if(i!=1){thrownewRuntimeException("Thenumberofdeletedbooksisnot1but:"+i);}}

ReadingRowsListing25-12showspseudocodesnippet(forfullcodeseethedownloadproject)toreadfromSQLiteusingtheSQLiteDatabase.query()method.ThismethodreturnsaCursorobject,whichyoucanusetoretrieveeachrow.

Listing25-12.AndroidSQLiteAPItoReadRecords

//Filereferenceinproject:BookPSSQLite.JavapublicList<Book>getAllBooks(){

//GetaccesstoareaddatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getReadableDatabase();

Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;//Getcolumnnamearrayfromthemetadataclass//(Seethedownloadhowthecolumnnamesaregathered)

//(attheendofthedayitisjustasetofcolumnnamesString[]colnames=BookSQLiteSQLiteMetaData.s_self.getColumnNames();

//SelectionStringselection=null;//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit

StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=null;

try{c=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);//Thismaynotbetheoptimalwaytoreaddatathroughalist//DirectlypassthecursorbackifyourintentistoreadtheseonerowatatimeList<Book>bookList=newArrayList<Book>();for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Log.d(tag,"Therearebooks");Bookb=newBook();

//..fillbasefieldsthesameway

b.setName(c.getString(c.getColumnIndex(BookSQLiteMetaData.NAME)));

b.setAuthor(c.getString(c.getColumnIndex(BookSQLiteMetaData.AUTHOR)));

b.setIsbn(c.getString(c.getColumnIndex(BookSQLiteMetaData.ISBN)));//..fillotherfields

//OryoucoulddelegatethisworktotheBookSQLiteMetaDataobject//aswehavedoneinthesampledownloadableproject//Ex:BookSQLiteSQLiteMetaData.s_self.fillFields(c,b);

bookList.add(b);}returnbookList;}

finally{if(c!=null)c.close();}}

HereareafewfactsaboutanAndroidcursorobject:

Acursorisacollectionofrows.

YouneedtousemoveToFirst()beforereadinganydatabecausethecursorstartsoffpositionedbeforethefirstrow.

Youneedtoknowthecolumnnames.

Youneedtoknowthecolumntypes.

Allfield-accessmethodsarebasedoncolumnnumber,soyoumustconvertthecolumnnametoacolumnnumberfirst.Notethatthislookupcanbeoptimized.It’smoreefficienttopopulatethecolumnnamearrayinorderifyouwishtofetchthevaluesandthenuseexplicitconstantindicesonthecursor.

Thecursorisrandom(youcanmoveforwardandbackward,andyoucanjump).

Becausethecursorisrandom,youcanaskitforarowcount.

ApplyingTransactionsSQLitelibrariesonAndroidsupporttransactions.ThetransactionmethodsareavailableontheSQLiteDatabaseclass.Thesemethodsareshowninpseudocodesnippet(forfullcodeseethedownloadproject)inListing25-13.

Listing25-13.SQLiteAPIforTransactions

//Filereferenceinproject:DBServicesProxyHandler.JavapublicvoiddoSomeUpdates(){SQLiteDatabasedb;//Getareferencetothisdatabasethroughhelperdb.beginTransaction();

try{//...callanumberofdatabasemethodsdb.setTransactionSuccessful();

}finally{db.endTransaction();

}}

SummarizingSQLite

IfyouareaJavaprogrammerwithafewyearsofexperience,whatwehavecoveredsofarissufficienttounderstandtheSQLiteAPIinAndroid.Withthematerialcoveredsofar,youknowhowtocheckforadatabase,createadatabasethroughDDL,insertrows,updaterows,deleterows,orreadusingdatabasecursors.WehavealsoshowedyouthebasicAPIfortransactions.However,ifyouarenotanexperiencedhandatJava,databasetransactionsaretrickytoimplementcorrectlyandefficiently.ThenextsectionwilltellyouanAPI-basedpatternusingJavadynamicproxies.

DoingTransactionsThroughDynamicProxiesYoucanvisualizeyourmobileapplicationasacollectionoftwobricks:AnAPIbrickandaUIbrick.TheAPIbrickwillhaveaseriesofstatelessmethodsthatprovidelogicanddatatotheUIbrick.InthiscontextthemethodinListing25-13doSomeUpdates()isconsideredareusableAPIbymanypartsoftheUIorbyotherAPIs.BecauseitisareusableAPItheclientdecideswhethersomethingshouldbecommittedornotcommittedinthattransaction.ThismeanstheAPIshouldnotbedealingwithtransactionsmostofthetime.Itisverymuchlikeastoredprocedureinarelationaldatabase.Astoredprocedurerarelydoestransactionsdirectly.Thecontainerofthestoredproceduredecidestocommitornotcommitexternaltothestoredprocedure.Thelogicisthis:ifthestoredprocedureisinvokedbyitselfthenitsoutputiscommittedatthestoredprocedurelevel.Ifthestoredprocedureiscalledbyanotherstoredprocedurethecommitwaitsuntilthemaininvokingstoredprocedureiscomplete.

ItisbettertousethesamestrategyfortheseAPIsinyourapplicationtoreducethecomplexityinimplementingtheAPIs.ThisisdonebyinterceptingcallstoalltheAPIstomakeadeterminationifthisisadirectcallorbeingcalledbyanotherAPIthatisalreadybeingmonitoredforatransaction.ThereareanumberofwaystointercepttheAPIcallsthatneedtobeintercepted.ThisisalsosometimescalledAspect-OrientedProgrammingorAOP.AOPneedssophisticatedtoolingtodo.Javaprovidesalesssophisticatedbutstraightforwardwaytodothisthroughdynamicproxies.AdynamicproxyisafacilityinJava,basedonJavareflection,thatallowsyoutointerceptcallstoanunderlyingobjectwithouttheobjectbeingawareofit.Whenaclientcallstheobjectthroughthisproxy,theclientthinksitistalkingtotheobjectdirectly.However,theproxycanchoosetoapplyotheraspects(likesecurity,logging,transactions,etc.)beforesendingthecalltotherealobject.Theincludedprojectforthischapterprovidesafullimplementationofadynamicproxythatautomaticallyappliesthetransactionalaspects.

WewillshowyoufirstwhatyourAPIimplementationslooklikeonceadynamicproxyisinplace.Thiswillgiveyouanideaofthesimplicityofthisapproachtotransactionsfirst.Thenyoucanseeifyouwanttotakethisrouteandusedynamicproxies.Aswepresentthecodebelownotethatwewillbeincludingonlysnippetsorsamplesandnottheentirecode.Usethedownloadableprojectforfulldetails.Wehaveannotatedthedownloadprojectwithalotofcommentstohelpyourunderstanding.WiththatcaveatconsidertheAPItoworkwithBook-basedobjects.

Listing25-14.API-BasedInterfacesforWorkingwiththeBookDomainObject

//Filereferenceinproject:IBookPS.Java

publicinterfaceIBookPS{publicintsaveBook(Bookbook);publicBookgetBook(intbookid);publicvoidupdateBook(Bookbook);publicvoiddeleteBook(intbookid);publicList<Book>getAllBooks();}

ThisinterfacedefinesoperationsusingJava-basedobjects.Theletters“PS”attheendofIBookPSserviceindicatesthatthisisapersistenceserviceAPIforabook.Listing25-15showsanSQLiteimplementationfortheIBookPS

Listing25-15.ImplementingtheBookAPIsUsingSQLite

//Filereferenceinproject:BookPSSQLite.Java//Themissingclassesinthiscodeareinthedownloadandnotessentialfor//exploringtheidea.//ASQLitePSisaclassthatcontainsreusablecommonmethodslikegettingaccess//tothereadandwritedatabasesusingthesingletondatabasehelper.publicclassBookPSSQLiteextendsASQLitePSimplementsIBookPS{

privatestaticStringtag="BookPSSQLite";@OverridepublicintsaveBook(Bookbook){

//getthedatabase//case:iddoesnotexistinthebookobjectif(book.getId()==-1){//idofthebookdoesn'texistsocreateitreturn(int)createBook(book);}//case:idexistsinbookobjectupdateBook(book);returnbook.getId();}@OverridepublicvoiddeleteBook(intbookid){

SQLiteDatabasedb=getWriteDb();Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;StringwhereClause=String.format("%s=%s;",BookSQLiteSQLiteMetaData.ID_COLNAME,bookid);String[]whereClauseargs=null;inti=db.delete(tname,whereClause,whereClauseargs);if(i!=1){thrownewRuntimeException("Thenumberofdeletedbooksisnot1but:"+i);}

}privatelongcreateBook(Bookbook){

//bookdoesn'texist//createitSQLiteDatabasedb=getWriteDb();

ContentValuesbcv=this.getBookAsContentValuesForCreate(book);

//Idon'tneedtoinsertanemptyrow//usuallyanynullablecolumnnamegoeshereifIwanttoinsertanemptyrow.StringnullColumnNameHack=null;//ConstructvaluesfromtheBookobject.SQLExceptionisaruntimeexceptionlongrowId=db.insertOrThrow(BookSQLiteMetaData.TABLE_NAME,nullColumnNameHack,bcv);returnrowId;}@OverridepublicvoidupdateBook(Bookbook){

if(book.getId()<0){thrownewSQLException("Bookidislessthan0");}SQLiteDatabasedb=getWriteDb();ContentValuesbcv=this.getBookAsContentValuesForUpdate(book);StringwhereClause=String.format("%s=%s",BookSQLiteMetaData.ID_COLNAME,book.getId());whereArgs[0]=BookSQLiteMetaData.ID_COLNAME;whereArgs[1]=Integer.toString(book.getId());

intcount=db.update(BookSQLiteMetaData.TABLE_NAME,bcv,whereClause,null);if(count==0){thrownewSQLException(String.format("Failedtoupdatebookforbookid:%s",book.getId()));}}privateContentValuesgetBookAsContentValuesForUpdate(Bookbook){

ContentValuescv=newContentValues();//Followingcodeloadscolumnvaluesfrombookobjecttothecv//SeethedownloadableprojectforthemechanicsofitBookSQLiteMetaData.s_self.fillUpdatableColumnValues(cv,book);

returncv;}privateContentValuesgetBookAsContentValuesForCreate(Bookbook){

ContentValuescv=newContentValues();BookSQLiteMetaData.s_self.fillAllColumnValues(cv,book);returncv;}@OverridepublicList<Book>getAllBooks(){

SQLiteDatabasedb=getReadDb();Stringtname=BookSQLiteMetaData.TABLE_NAME;String[]colnames=BookSQLiteMetaData.s_self.getColumnNames();

//SelectionStringselection=null;//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit

StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=null;

try{c=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);//Thismaynotbetheoptimalwaytoreaddatathroughalist//DirectlypassthecursorbackifyourintentistoreadtheseonerowatatimeList<Book>bookList=newArrayList<Book>();for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Log.d(tag,"Therearebooks");Bookb=newBook();BookSQLiteMetaData.s_self.fillFields(c,b);bookList.add(b);}returnbookList;}finally{if(c!=null)c.close();}

}@OverridepublicBookgetBook(intbookid){

SQLiteDatabasedb=getReadDb();Stringtname=BookSQLiteMetaData.TABLE_NAME;String[]colnames=BookSQLiteMetaData.s_self.getColumnNames();

//SelectionStringselection=String.format("%s=%s",BookSQLiteMetaData.ID_COLNAME,bookid);//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit

StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);try{if(c.isAfterLast()){Log.d(tag,"Norowsforid"+bookid);returnnull;}Bookb=newBook();BookSQLiteMetaData.s_self.fillFields(c,b);returnb;}finally{c.close();}}}//eof-class

NoticehowtheimplementationoftheBookpersistenceAPIdoesnotdirectlydealwiththetransactionalaspectsofthesemethods.Instead,thetransactionsarehandledbyJavadynamicproxy,whichwewillshowshortly.Listing25-16showshowaclientcanseetheseAPIsandinvokethesepersistenceAPIsindirectly(again,pleaserefertothedownloadprojectsforclassesthatarereferencedinthiscodebutnotlistedhereastheyarenotessentialforunderstanding).

Listing25-16.ClientAccesstoAPI-BasedServices

//Filereferenceinproject:SQLitePersistenceTester.Java

//BaseTesterisjustahelperclasstoprovidercommonfunctionality//itimplementssomeloggingandreportbackmethodstotheUIactivitypublicclassSQLitePersistenceTesterextendsBaseTester{

privatestaticStringtag="SQLitePersistenceTester";//Servicesisastaticclassthatprovidesaccesstopersistenceservices//ServicesclassprovidesvisibilitytotheimplementeroftheIBookPS//Itdemonstrateshowaclientgetsaccesstothenamespaceofservices//Youwillshortlyseewhatthisclassis.Understandtheintentfirst.privateIBookPSbookPersistenceService

=Services.PersistenceServices.bookps;

//IReportBackisalogginginterfacetoreportloggableeventsbacktotheUI//UIwillthenchoosetologthoseeventsandalsoshowontheactivityscreen.SQLitePersistenceTester(Contextctx,IReportBacktarget){super(ctx,target,tag);}

//Addabookwhoseidisonelargerthanthebooks//inthedatabasepublicvoidaddBook(){

Bookbook=Book.createAMockBook();intbookid=bookPersistenceService.saveBook(book);

reportString(String.format("Insertedabook%swhosegeneratedidnowis%s",book.getName(),bookid));}//DeletethelastbookpublicvoidremoveBook(){List<Book>bookList=bookPersistenceService.getAllBooks();if(bookList.size()<=0){reportString("Therearenobooksthatcanbedeleted");return;}reportString(String.format("Thereare%sbooks.Firstonewillbedeleted",bookList.size()));

Bookb=bookList.get(0);bookPersistenceService.deleteBook(b.getId());reportString(String.format("Bookwithid:%ssuccessfullydeleted",b.getId()));}

//writethelistofbookssofartothescreenpublicvoidshowBooks(){

List<Book>bookList=bookPersistenceService.getAllBooks();

reportString(String.format("Numberofbooks:%s",bookList.size()));for(Bookb:bookList){reportString(String.format("id:%sname:%sauthor:%sisbn:%s",b.getId(),b.getName(),b.getAuthor(),b.getIsbn()));}}

//CountthenumberofbooksinthedatabaseprivateintgetCount(){

List<Book>bookList=bookPersistenceService.getAllBooks();

returnbookList.size();}}

InListing25-16,noticehowsimpleitistoaccesstheAPIsthroughthestaticclassServices.Ofcoursewehaven’tshownyoutheimplementationofServicesandalsothedynamicproxyheldbythestaticclassServices.Listing25-17showsthesourcecodeforthestaticServicesclassinordertogiveyouanideaofhowthisschemeworks.Thegoalofmany,ifnotall,ofthelistingsinthischapteristoaidyourunderstanding.Forcompletecompilablesourcecodewekindlyrequestthatyourefertothedownloadableprojectsforthischapter.

Listing25-17.ExposingAPIstoClientsThroughaServicesNameSpace

//Filereferenceinproject:Services.Java/***Allowanamespaceforclientstodiscovervariousservices*Usage:Services.persistenceServices.bookps.addBook();etc.

*Dynamicproxywilltakecareoftransactions.*Dynamicproxywilltakecareofmockdata.*DynamicProxywillallowmorethanoneinterface*toapplytheaboveaspects.*/

publicclassServices{

publicstaticStringtag="Services";publicstaticclassPersistenceServices{

////sethispointerduringinitializationpublicstaticIBookPSbookps=null;

static{Services.init();}}//Althoughthismethodisempty,callingit//willtriggerallstaticinitializationcodeforthisclasspublicstaticvoidinit(){}privatestaticObjectmainProxy;static{//Autilityclasstocompilealldatabase-relatedinitializationssofar//Getsthedatabasehelpergoing.//SeethedownloadprojecthowitusestheconceptspresentedsofartodothisDatabase.initialize();

//setupbookpsClassLoadercl=IBookPS.class.getClassLoader();//AddmoreinterfacesasavailableClass[]variousServiceInterfaces=newClass[]{IBookPS.class};

//Createabigobjectthatcanproxyalltherelatedinterfaces//forwhichsimilarcommonaspectsareapplied//InthiscasesitisandroidSQLitetransactionsmainProxy=Proxy.newProxyInstance(cl,

variousServiceInterfaces,newDBServicesProxyHandler());

//PresetthenamespaceforeasydiscoveryPersistenceServices.bookps=(IBookPS)mainProxy;}}

NoticehowDBServicesProxyHandlerisaproxyfortheimplementationofIBookPS.Whencalledbyclients,theDBServicesProxyHandlerthencallstheactualimplementationforIBookPS.TheactualimplementationofIBookPSisshowninListing25-15.Let’sturntotheimplementationofthedynamicproxyinListing25-18.SomeofthecodeandclassesreferencedinListing25-18areonlyavailableinthedownloadableprojects.However,thatshouldnothinderthegeneralunderstandingofthearchitectureofthedynamicproxy.

Listing25-18.JavaDynamicProxytoWraptheSQLiteAPIImplementations

//Filereferenceinproject:DBServicesProxyHandler.Java/***DBServicesProxyHandler:AclasstoexternalizeSQLiteTransactions.*Itisadynamicproxy.SeeServices.Javatoseehowareferencetothisisused.**Thisproxyiscapableofhostingmultiplepersistenceinterfaces.*Eachinterfacemayrepresentpersistenceaspectsofaparticularentityoradomainobject*likeaBook.Ortheinterfacecanbeacompositeinterfacedealingwithmultipleentities.**ItalsousesThreadLocalstopasstheDatabaseContext*DatabaseContextholdsareferencetothedatabasethatisonthisthread*Italsoknowshowtoapplytransactionstothatdatabase*Italsoknowsifthecurrentthreadalsohasarunningtransaction*@SeeDatabaseContext**DatabaseContextprovidestheSQLiteDatabasereferenceto*theimplementationclasses.**Relatedclasses******************Services.Java:Clientaccesstointerfaces*IBookPS:ClientinterfacetodealwithpersistingaBook*BookPSSQLite:SQLiteImplementationofIBookPS**DBServicesProxyHandler:Thisclassthatisadynamicproxy*DatabaseContext:HoldsadbreferenceforBookPSSQLiteimplementation*DirectAccessBookDBHelper:AndroidDBHelpertoconstructthedatabase**/publicclassDBServicesProxyHandlerimplementsInvocationHandler{

privateBookPSSQLitebookServiceImpl=newBookPSSQLite();privatestaticStringtag="DBServicesProxyHandler";DBServicesProxyHandler(){}publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)

throwsThrowable{logMethodSignature(method);Stringmname=method.getName();if(mname.startsWith("get")){returnthis.invokeForReads(method,args);}else{returnthis.invokeForWrites(method,args);}}privatevoidlogMethodSignature(Methodmethod){

StringinterfaceName=method.getDeclaringClass().getName();Stringmname=method.getName();Log.d(tag,String.format("%s:%s",interfaceName,mname));}privateObjectcallDelegatedMethod(Methodmethod,Object[]args)

throwsThrowable{returnmethod.invoke(bookServiceImpl,args);}privateObjectinvokeForReads(Methodmethod,Object[]args)throws

Throwable{

//SeecommentsaboveaboutDatabaseContextif(DatabaseContext.isItAlreadyInsideATransaction()==true){//ItisalreadyboundreturninvokeForReadsWithoutATransactionalWrap(method,args);}else{//AnewtransactionreturninvokeForReadsWithATransactionalWrap(method,args);}

}privateObjectinvokeForReadsWithATransactionalWrap(Methodmethod,

Object[]args)

throwsThrowable{try{DatabaseContext.setReadableDatabaseContext();returncallDelegatedMethod(method,args);}finally{DatabaseContext.reset();}

}privateObjectinvokeForReadsWithoutATransactionalWrap(Methodmethod,

Object[]args)

throwsThrowable{returncallDelegatedMethod(method,args);}privateObjectinvokeForWrites(Methodmethod,Object[]args)throws

Throwable{

if(DatabaseContext.isItAlreadyInsideATransaction()==true){//ItisalreadyboundreturninvokeForWritesWithoutATransactionalWrap(method,args);}else{//AnewtransactionreturninvokeForWritesWithATransactionalWrap(method,args);}}privateObjectinvokeForWritesWithATransactionalWrap(Methodmethod,

Object[]args)

throwsThrowable{try{DatabaseContext.setWritableDatabaseContext();DatabaseContext.beginTransaction();ObjectrtnObject=callDelegatedMethod(method,args);DatabaseContext.setTransactionSuccessful();returnrtnObject;}finally{try{DatabaseContext.endTransaction();}finally{DatabaseContext.reset();}}}privateObjectinvokeForWritesWithoutATransactionalWrap(Methodmethod,

Object[]args)

throwsThrowable{returncallDelegatedMethod(method,args);}}//eof-class

ThiscodeinListing25-18isthedynamicproxyimplementation.Wehavenotincludedallthedetailsbutsufficientdetailisheretounderstandhowthisdynamicproxyperforms

transactionsinanautomatedaspect-orientedway.Itexaminesthecalledmethodnamethroughreflectiontoseeifthemethodnamestartswith“get,”andifsothenitassumesthemethoddoesn’tneedatransactionalcontext.Otherwiseitmarksthecurrentthreadasatransactionalcontext.Atthereturnofthemethoditcompletesthetransactionassuccessful.Ifthereareothermethodscalledinbetween,thedynamicproxyknowsfromthethreadthatthereisatransactioninplaceandhenceignoresthatmethodfromatransactionalaspectperspective.

Nowbasedonyourneedyoumaywanttoalterthisprotocolbasedonannotationsorsomeotheraspectofyourinterfaces,butyougettheidea.ThisapproachofseparatingAPIsfromyourUIisgooddesignandyoucanuseanynumberofpersistentstoreswithoutchangingyourclientUIcode.Westronglyrecommendthatyouadaptthisapproachirrespectiveofthepersistencemechanismyouuse,includingtheO/Rmappingtools.

ExploringDatabasesontheEmulatorandAvailableDevicesAsyouuseSQLiteasyourpersistencemechanismeitherdirectlyorthroughcontentproviders(nextsection),youmaywanttoexaminetheresultingdatabasefilesonthedevicefordebuggingpurposes.

ThedatabasefilescreatedbySQLiteAPIarekeptinthefollowingdirectory:

/data/data/<fully-qualified-package-name>/databases

YoucanuseEclipseAndroidfileexplorertolocatethedirectoryandcopythefilestoyourlocaldriveandusenativeSQLitetoolsprovidedbySQLitedirectlytoseeandmanipulatethatdatabase.

YoucanalsousetoolsprovidedbothbyAndroidandbySQLitetoexaminethesedatabases.Manyofthesetoolsresideinthe\<android-sdk-install-directory>\toolssubdirectory;othersarein\<android-sdk-install-directory>\platform-tools.

Someusefulcommandsfromthesedirectoriesare

androidlistavd:ToseealistofAVDsoremulatorsemulator.exe@avdname:Tostartanemulatorwithagivennameadb.exedevices:Toseethedevicesoremulatorsadbshell:Toopenashellontheemulatorordevice

Youcanusethefollowingcommandsfroman“adbshell.”Thesewillworkontheemulatorbutonarealdeviceyouwillneedrootaccess.

ls/system/bin:Toseeavailablecommandsls-l/:Rootleveldirectoriesls/data/data/com.android.providers.contacts/databases:anexample

ls-R/data/data/*/databases:Toseealldatabasesonthedeviceoremulator

IftherewereafindcommandintheincludedAndroidUnixshell,youcouldlookatallthe*.dbfiles.Butthereisnogoodwaytodothiswithlsalone.Thenearestthingyoucandoisthis:

ls-R/data/data/*/databases

Withthiscommand,youwillnoticethattheAndroiddistributionhasthedatabasesshowninListing25-19(dependingonyourrelease,thislistmayvary):

Listing25-19.AFewSampleDatabases

alarms.dbcontacts.dbdownloads.dbinternal.dbsettings.dbmmssms.dbtelephony.db

Youcaninvokesqlite3ononeofthesedatabasesinsidetheadbshellbytypingthis:

sqlite3/data/data/com.android.providers.contacts/databases/contacts.db

Youcanexitsqlite3bytypingthis:

sqlite>.exit

Noticethatthepromptforadbis#andthepromptforsqlite3issqlite>.Thesepromptscouldbedifferentdependingonthedevice.Youcanreadaboutthevarioussqlite3commandsbyvisitingwww.sqlite.org/sqlite.html.However,wewilllistafewimportantcommandsheresoyoudon’thavetomakeatriptotheWeb.Youcanseealistoftablesbytyping

sqlite>.tables

Thiscommandisashortcutforqueryingonthesqlite_mastertableasshowninListing25-20(formatandstructureoftheresultingoutputmayvary).

Listing25-20.UsingSQLitesqlite_masterTable

SELECTnameFROMsqlite_masterWHEREtypeIN('table','view')ANDnameNOTLIKE'sqlite_%'UNIONALLSELECTnameFROMsqlite_temp_masterWHEREtypeIN('table','view')ORDERBY1

Thetablesqlite_masterisamastertablethatkeepstrackoftablesandviewsintheSQLitedatabase.Thefollowingcommandlinedisplaysacreatestatementforatablecalledpeopleincontacts.db(assumingthisdatabaseexistsonyourdevice):

.schemapeople

ThisisonewaytogetatthecolumnnamesofatableinSQLite.Thiswillalsoshowthecolumndatatypes.Whileworkingwithcontentproviders,youshouldnotethesecolumntypesbecauseaccessmethodsdependonthem.Alsonotethatthismaynotbeapracticalwaytoseethesedatabasesasyoumaynothaveaccesstothemonrealdevices.Inthatcaseyouhavetorelyonthedocumentationprovidedbythecontentprovider.

YoucanissuethefollowingcommandfromyourOScommandprompttopulldownthecontacts.dbfiletothelocalfilesystem:

adbpull/data/data/com.android.providers.contacts/databases/contacts.dbÉc:/somelocaldir/contacts.db

ThesampleSQLstatementsinListing25-21couldhelpyounavigatethroughanSQLitedatabasequickly(alternativelyyoucanuseanythird-partySQLitebrowsertool):

Listing25-21.SampleSQLCodeforSQLite

--Setthecolumnheaderstoshowinthetoolsqlite>.headerson

--selectallrowsfromatableselect*fromtable1;

--countthenumberofrowsinatableselectcount(*)fromtable1;

--selectaspecificsetofcolumnsselectcol1,col2fromtable1;

--Selectdistinctvaluesinacolumnselectdistinctcol1fromtable1;

--countingthedistinctvaluesselectcount(col1)from(selectdistinctcol1fromtable1);

--groupbyselectcount(*),col1fromtable1groupbycol1;

--regularinnerjoinselect*fromtable1t1,table2t2wheret1.col1=t2.col1;

--leftouterjoin--Givemeeverythingint1eventhoughtherearenorowsint2select*fromtablet1leftouterjointable2t2ont1.col1=t2.col1where….

ExploringContentProvidersEarlierinthechapterwetoucheduponcontentproviderstosharedatabetweenapplications.Contentprovidersasstatedarewrappersaroundadatastore.Thedatastorescouldbelocalorremote.ThedatastoresareusuallySQLitedatabasesonthelocaldevice.

Toretrievedatafromacontentproviderorsavedataintoacontentprovider,youwilluseasetofREST-likeURIs.Forexample,ifyouweretoretrieveasetofbooksfromacontentproviderthatisanencapsulationofabookdatabase,youmightneedtouseaURIlikethis:

content://com.android.book.BookProvider/books

Toretrieveaspecificbookfromthebookdatabase(likesaybook23),youmightuseaURIlikethis:

content://com.android.book.BookProvider/books/23

YouwillseeinthischapterhowtheseURIstranslatetounderlyingdatabase-accessmechanisms.AnyapplicationwiththeappropriateaccesspermissionsonthedevicecanmakeuseoftheseURIstoaccessandmanipulatedata.

ExploringAndroid’sBuilt-inProvidersAndroidcomeswithanumberofbuilt-incontentproviders,whicharedocumentedintheSDK’sandroid.providerJavapackage.Youcanviewthelistoftheseprovidershere:

http://developer.android.com/reference/android/provider/package-summary.html

Theprovidersinclude,forexample,ContactsandMediaStore.TheseSQLitedatabasestypicallyhaveanextensionof.dbandareaccessibleonlyfromtheimplementationpackage.Anyaccessoutsidethatpackagemustgothroughthecontent-providerinterface.Youcanusetheprevioussection“ExploringDatabasesontheEmulatorandAvailableDevices”toexplorethedatabasefilescreatedbybuilt-inprovidersontheemulator.Onrealdevicesthisisnotfeasibleunlessofcourseyouhaverootaccessonthedevice.

UnderstandingtheStructureofContentProvider

URIsEachcontentprovideronadeviceisregisteredintheAndroidmanifestfilelikeawebsitewithastringidentifiercalledanauthority(akintoadomainname).Listing25-22hastwoexamplesofthisregistration:

Listing25-22.ExampleofRegisteringaProvider

<!--Filereferenceinproject:AndroidManifest.xml--><providerandroid:name="SomeProviderJavaClass"android:authorities="com.your-company.SomeProvider"/>

<providerandroid:name="BookProvider"

android:authorities="com.androidbook.provider.BookProvider"/>

TheuniqueauthoritystringformsthebasisofasetofURIsthatthiscontentprovideroffers.AnAndroidcontentURIhasthefollowingstructure:

content://<authority-name>/<path-segment1>/<path-segment2>/etc…

Here’sanexampleURIthatidentifiesabooknumbered23inadatabaseofbooks:

content://com.androidbook.provider.BookProvider/books/23

Aftercontent:,theURIcontainstheauthority,whichisusedtolocatetheproviderintheproviderregistry.Intheprecedingexample,com.androidbook.provider.BookProvideristheauthorityportionoftheURI.

/books/23isthepathsectionoftheURIthatisspecifictoeachprovider.Thebooksand23portionsofthepathsectionarecalledpathsegments.ItistheresponsibilityoftheprovidertodocumentandinterpretthepathsectionandpathsegmentsoftheURIs.HencecontentprovidersprovidetheseREST-likeURLstoretrieveormanipulatedata.Fortheprecedingregistration,theURItoidentifyadirectoryoracollectionofbooksinthebooksdatabaseis

content://com.androidbook.provider.BookProvider/books

TheURItoidentifyaspecificnoteis

content://com.androidbook.provider.BookProvider/books/#

where#istheidofaparticularnote.Listing25-23showsadditionalexamplesofURIsthatsomedataprovidersonAndroidaccept:

Listing25-23.FewSampleAndroidContentURLs

content://media/internal/images

content://media/external/imagescontent://contacts/people/content://contacts/people/23

Noticehowtheseproviders’media(content://media)andcontacts(content://contacts)don’thaveafullyqualifiedauthorityname.ThisisbecauseprovidersofferedbyAndroidmaynotcarryafullyqualifiedauthorityname.

GiventhesecontentURIs,aproviderisexpectedtoretrieverowsthattheURIsrepresent.TheproviderisalsoexpectedtoaltercontentatthisURIusinganyofthestate-changemethods:insert,update,ordelete.

ImplementingContentProvidersLet’sfullyunderstandcontentprovidersbyimplementingandusingone.Towriteacontentprovider,youhavetoextendandroid.content.ContentProviderandimplementthefollowingkeymethods:query(),insert(),update(),delete(),andgetType().

You’llneedtosetupanumberofthingsforimplementingthesemethods.Implementingacontentproviderneedsthefollowingsteps:

1. Planyourdatabase,URIs,columnnames,andsoon,andcreateametadataclassthatdefinesconstantsforallofthesemetadataelements.

2. ExtendtheabstractclassContentProvider.

3. Implementthesemethods:query,insert,update,delete,andgetType.

4. Registertheproviderinthemanifestfile.

5. Usethecontentprovider.

PlanningaDatabaseToexplorethistopic,we’llcreateadatabasesimilartotheonethatwehaveusedforthebookcollectionthatwasusedtoillustratethestoringofdatainSQLitedirectly.Notethattokeepthedatabasesfromconflictingwitheachothersomeofthenamesmaybedifferent.

Thebookdatabasecontainsonlyonetablecalledbooks,anditscolumnsarename,isbn,andauthor.Thesecolumnnamesfallundermetadata.You’lldefinethissortofrelevantmetadatainaJavaclass.Thismetadata-bearingJavaclassBookProviderMetaDataisshowninListing25-24.

Listing25-24.DefiningMetadataforYourDatabase

//Filereferenceinproject:BookProviderMetaData.Java

publicclassBookProviderMetaData{

publicstaticfinalStringAUTHORITY="com.androidbook.provider.BookProvider";

publicstaticfinalStringDATABASE_NAME="book.db";publicstaticfinalintDATABASE_VERSION=1;publicstaticfinalStringBOOKS_TABLE_NAME="books";

privateBookProviderMetaData(){}

//innerclassdescribingBookTablepublicstaticfinalclassBookTableMetaDataimplementsBaseColumns{privateBookTableMetaData(){}publicstaticfinalStringTABLE_NAME="books";

//uriandMIMEtypedefinitionspublicstaticfinalUriCONTENT_URI=Uri.parse("content://"+AUTHORITY+"/books");publicstaticfinalStringCONTENT_TYPE=

"vnd.android.cursor.dir/vnd.androidbook.book";publicstaticfinalStringCONTENT_ITEM_TYPE="vnd.android.cursor.item/vnd.androidbook.book";

publicstaticfinalStringDEFAULT_SORT_ORDER="modifiedDESC";

//AdditionalColumnsstarthere.//stringtypepublicstaticfinalStringBOOK_NAME="name";//stringtypepublicstaticfinalStringBOOK_ISBN="isbn";//stringtypepublicstaticfinalStringBOOK_AUTHOR="author";//IntegerfromSystem.currentTimeMillis()publicstaticfinalStringCREATED_DATE="created";//IntegerfromSystem.currentTimeMillis()publicstaticfinalStringMODIFIED_DATE="modified";}}

ThisBookProviderMetaDataclassstartsbydefiningitsauthoritytobecom.androidbook.provider.BookProvider.

Thisclassthenproceedstodefineitsonetable(books)asaninnerBookTableMetaDataclass.TheBookTableMetaDataclassthendefinesaURIfor

identifyingacollectionofbooks.Giventheauthorityinthepreviousparagraph,theURIforacollectionofbookswilllooklikethis:

content://com.androidbook.provider.BookProvider/books

ThisURIisindicatedbytheconstant

BookProviderMetaData.BookTableMetaData.CONTENT_URI

TheBookTableMetaDataclassthenproceedstodefinetheMIMEtypesforacollectionofbooksandasinglebook.TheproviderimplementationwillusetheseconstantstoreturntheMIMEtypesfortheincomingURIs.MIMEtypesaresimilartotheMIMEtypesdefinedbyHTTP.AsaguidelinetheprimaryMIMEtypeforacollectionofitemsreturnedthroughanAndroidcursorshouldalwaysbevnd.android.cursor.dir,andtheprimaryMIMEtypeofasingleitemretrievedthroughanAndroidcursorshouldbevnd.android.cursor.item.Youhavemorewiggleroomwhenitcomestothesubtype,asinvnd.androidbook.bookinListing25-24.

BookTableMetaDatathendefinesthesetofcolumnsforthebooktable:name,isbn,author,created(creationdate),andmodified(last-updateddate).

ThemetadataclassBookTableMetaDataalsoinheritsfromtheBaseColumnsclassthatprovidesthestandard_idfield,whichrepresentstherowID.Withthesemetadatadefinitionsinhand,we’rereadytotackletheproviderimplementation.

ExtendingContentProviderImplementingtheBookProviderinvolvesextendingtheContentProviderclassandoverridingonCreate()tocreatethedatabaseandthenimplementthequery,insert,update,delete,andgetTypemethods.

Aquerymethodrequiresthesetofcolumnsitneedstoreturn.Thisissimilartoaselectclausethatrequirescolumnnamesalongwiththeirascounterparts(sometimescalledsynonyms).AsaconventionAndroidSDKusesamapobjectthatitcallsaprojectionmaptorepresentthesecolumnnamesandtheirsynonyms.Wewillneedtosetupthismapsowecanuseitlaterinthequery-methodimplementation.Inthecodefortheproviderimplementation(seeListing25-26),youwillseethisdoneupfrontaspartofprojectionmapsetup.

Mostofthemethodswe’llbeimplementingforthecontentprovidercontracttakeaURIasaninput.Listing25-25showsbookproviderURIexamples:

Listing25-25.ExamplesofBookProviderContentURIs

Uri1:content://com.androidbook.provider.BookProvider/booksUri2:content://com.androidbook.provider.BookProvider/books/12

ThebookproviderneedstodistinguisheachoftheseURIs.BookProviderisasimplecase.Ifourbookproviderhadbeenhousingmoreobjectsinadditiontojustbooks,thentherewouldbemoreURIstoidentifythoseadditionalobjects.

TheproviderimplementationneedsamechanismtodistinguishoneURIfromtheother;AndroidusesaclasscalledUriMatcherforthispurpose.SoweneedtosetupthisobjectwithallourURIvariations.YouwillseethiscodeinListing25-26rightafterwedefinetheprojectionmap.We’llfurtherexplaintheUriMatcherclassinthesection“UsingUriMatchertoFigureOuttheURIs.”

ThecodeinListing25-26thenoverridestheonCreate()methodtofacilitatethedatabasecreation.ThedatabasecreationisidenticaltothedatabasecreationwehavecoveredaspartofusingSQLitedirectlyforinternalpersistenceneeds.

ThesourcecodeinListing25-26thenimplementstheinsert(),query(),update(),getType(),anddelete()methods.ThecodeforallofthisispresentedtogetherinListing25-26,butwewillexplaineachaspectinaseparatesubsection.

Listing25-26.ImplementingtheBookProviderContentProvider

//Filereferenceinproject:BookProvider.JavapublicclassBookProviderextendsContentProvider{//Logginghelpertag.Nosignificancetoproviders.privatestaticfinalStringTAG="BookProvider";

//SetupprojectionMap//Projectionmapsaresimilarto"as"(columnalias)construct//inansqlstatementwherebyyoucanrenamethe//columns.privatestaticHashMap<String,String>sBooksProjectionMap;

static{sBooksProjectionMap=newHashMap<String,String>();sBooksProjectionMap.put(BookTableMetaData._ID,BookTableMetaData._ID);

//name,isbn,authorsBooksProjectionMap.put(BookTableMetaData.BOOK_NAME,BookTableMetaData.BOOK_NAME);sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN,BookTableMetaData.BOOK_ISBN);sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR,BookTableMetaData.BOOK_AUTHOR);

//createddate,modifieddatesBooksProjectionMap.put(BookTableMetaData.CREATED_DATE,BookTableMetaData.CREATED_DATE);

sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE,BookTableMetaData.MODIFIED_DATE);}

//Provideamechanismtoidentifyalltheincominguripatterns.

privatestaticfinalUriMatchersUriMatcher;privatestaticfinalintINCOMING_BOOK_COLLECTION_URI_INDICATOR=1;privatestaticfinalintINCOMING_SINGLE_BOOK_URI_INDICATOR=2;static{sUriMatcher=newUriMatcher(UriMatcher.NO_MATCH);sUriMatcher.addURI(BookProviderMetaData.AUTHORITY,"books",INCOMING_BOOK_COLLECTION_URI_INDICATOR);sUriMatcher.addURI(BookProviderMetaData.AUTHORITY,"books/#",INCOMING_SINGLE_BOOK_URI_INDICATOR);

}//Setup/CreateDatabasetousefortheimplementationprivatestaticclassDatabaseHelperextendsSQLiteOpenHelper{

DatabaseHelper(Contextcontext){super(context,BookProviderMetaData.DATABASE_NAME,null,BookProviderMetaData.DATABASE_VERSION);}@OverridepublicvoidonCreate(SQLiteDatabasedb){Log.d(TAG,"inneroncreatecalled");db.execSQL("CREATETABLE"+BookTableMetaData.TABLE_NAME+"("+BookTableMetaData._ID+"INTEGERPRIMARYKEY,"+BookTableMetaData.BOOK_NAME+"TEXT,"+BookTableMetaData.BOOK_ISBN+"TEXT,"+BookTableMetaData.BOOK_AUTHOR+"TEXT,"+BookTableMetaData.CREATED_DATE+"INTEGER,"+BookTableMetaData.MODIFIED_DATE+"INTEGER"+");");}@OverridepublicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){

Log.d(TAG,"inneronupgradecalled");Log.w(TAG,"Upgradingdatabasefromversion"+oldVersion+"to"+newVersion+",whichwilldestroyallolddata");db.execSQL("DROPTABLEIFEXISTS"+BookTableMetaData.TABLE_NAME);onCreate(db);}}//eof-innerDatabaseHelperclass//ThisisinitializedintheonCreate()methodprivateDatabaseHelpermOpenHelper;

//Componentcreationcallback@OverridepublicbooleanonCreate(){

Log.d(TAG,"mainonCreatecalled");mOpenHelper=newDatabaseHelper(getContext());returntrue;}

@OverridepublicCursorquery(Uriuri,String[]projection,Stringselection,

String[]selectionArgs,StringsortOrder){SQLiteQueryBuilderqb=newSQLiteQueryBuilder();

switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:qb.setTables(BookTableMetaData.TABLE_NAME);qb.setProjectionMap(sBooksProjectionMap);break;

caseINCOMING_SINGLE_BOOK_URI_INDICATOR:qb.setTables(BookTableMetaData.TABLE_NAME);qb.setProjectionMap(sBooksProjectionMap);qb.appendWhere(BookTableMetaData._ID+"="+uri.getPathSegments().get(1));break;

default:thrownewIllegalArgumentException("UnknownURI"+uri);}

//IfnosortorderisspecifiedusethedefaultStringorderBy;if(TextUtils.isEmpty(sortOrder)){orderBy=BookTableMetaData.DEFAULT_SORT_ORDER;

}else{orderBy=sortOrder;}

//GetthedatabaseandrunthequerySQLiteDatabasedb=mOpenHelper.getReadableDatabase();Cursorc=qb.query(db,projection,selection,selectionArgs,null,null,orderBy);

//exampleofgettingacountinti=c.getCount();

//Tellthecursorwhaturitowatch,//soitknowswhenitssourcedatachangesc.setNotificationUri(getContext().getContentResolver(),uri);returnc;}@OverridepublicStringgetType(Uriuri){

switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:returnBookTableMetaData.CONTENT_TYPE;caseINCOMING_SINGLE_BOOK_URI_INDICATOR:returnBookTableMetaData.CONTENT_ITEM_TYPE;default:thrownewIllegalArgumentException("UnknownURI"+uri);}}@OverridepublicUriinsert(Uriuri,ContentValuesinitialValues){

//Validatetherequesteduriif(sUriMatcher.match(uri)!=INCOMING_BOOK_COLLECTION_URI_INDICATOR){thrownewIllegalArgumentException("UnknownURI"+uri);}ContentValuesvalues;if(initialValues!=null){values=newContentValues(initialValues);}else{values=newContentValues();}Longnow=Long.valueOf(System.currentTimeMillis());//Makesurethatthefieldsareallsetif(values.containsKey(BookTableMetaData.CREATED_DATE)

==false){values.put(BookTableMetaData.CREATED_DATE,now);}if(values.containsKey(BookTableMetaData.MODIFIED_DATE)==false){values.put(BookTableMetaData.MODIFIED_DATE,now);}if(values.containsKey(BookTableMetaData.BOOK_NAME)==false){thrownewSQLException("FailedtoinsertrowbecauseBookNameisneeded"+uri);}if(values.containsKey(BookTableMetaData.BOOK_ISBN)==false){values.put(BookTableMetaData.BOOK_ISBN,"UnknownISBN");}if(values.containsKey(BookTableMetaData.BOOK_AUTHOR)==false){values.put(BookTableMetaData.BOOK_ISBN,"UnknownAuthor");}

SQLiteDatabasedb=mOpenHelper.getWritableDatabase();longrowId=db.insert(BookTableMetaData.TABLE_NAME,BookTableMetaData.BOOK_NAME,values);if(rowId>0){UriinsertedBookUri=ContentUris.withAppendedId(BookTableMetaData.CONTENT_URI,rowId);getContext().getContentResolver().notifyChange(insertedBookUri,null);

returninsertedBookUri;}thrownewSQLException("Failedtoinsertrowinto"+uri);}@Overridepublicintdelete(Uriuri,Stringwhere,String[]whereArgs){

SQLiteDatabasedb=mOpenHelper.getWritableDatabase();intcount;switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:

count=db.delete(BookTableMetaData.TABLE_NAME,where,whereArgs);break;caseINCOMING_SINGLE_BOOK_URI_INDICATOR:StringrowId=uri.getPathSegments().get(1);count=db.delete(BookTableMetaData.TABLE_NAME,BookTableMetaData._ID+"="+rowId+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);break;default:thrownewIllegalArgumentException("UnknownURI"+uri);}

getContext().getContentResolver().notifyChange(uri,null);returncount;}@Overridepublicintupdate(Uriuri,ContentValuesvalues,

Stringwhere,String[]whereArgs){SQLiteDatabasedb=mOpenHelper.getWritableDatabase();intcount;switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:count=db.update(BookTableMetaData.TABLE_NAME,values,where,whereArgs);break;

caseINCOMING_SINGLE_BOOK_URI_INDICATOR:StringrowId=uri.getPathSegments().get(1);count=db.update(BookTableMetaData.TABLE_NAME,values,BookTableMetaData._ID+"="+rowId+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);break;

default:thrownewIllegalArgumentException("UnknownURI"+uri);}

getContext().getContentResolver().notifyChange(uri,null);

returncount;}}

Now,let’sanalyzethiscodesectionbysection.

UsingUriMatchertoFigureOuttheURIsWe’vementionedtheUriMatcherclassseveraltimesnow;let’slookintoit.AlmostallmethodsinacontentproviderareoverloadedwithrespecttotheURI.Forexample,thesamequery()methodiscalledwhetheryouwanttoretrieveasinglebookoralistofbooks.ItisuptothemethodtoknowwhichtypeofURIisbeingrequested.Android’sUriMatcherutilityclasshelpsyouidentifytheURItypes.

Here’showitworks.YoutellaninstanceofUriMatcherwhatkindofURIpatternstoexpectduringitsinitialization.Youwillalsoassociateauniquenumberwitheachpattern.Oncethesepatternsareregistered,youcanthenaskUriMatcheriftheincomingURImatchesacertainpattern.

Aswe’vementioned,ourBookProvidercontentproviderhastwoURIpatterns:oneforacollectionofbooksandoneforasinglebook.ThecodeinListing25-26registersbothofthesepatternsusingUriMatcher.Itallocates1foracollectionofbooksand2forasinglebook(theURIpatternsthemselvesaredefinedinthemetadataforthebookstable).YoucanseethisinthestaticinitializationofthevariablesUriMatcherinListing25-26.YoucanthenseehowUriMatcherplaysapartinthequery()methodimplementationindistinguishingtheURIsusingtheconstantsforeachtypeofURI.

UsingProjectionMapsAcontentprovideractslikeanintermediarybetweenanabstractsetofcolumnsandarealsetofcolumnsinadatabase,yetthesecolumnsetscoulddiffer.Whileconstructingqueries,youmustmapbetweenthewhereclausecolumnsthataclientspecifiesandtherealdatabasecolumns.YousetupthisprojectionmapwiththehelpoftheSQLiteQueryBuilderclass.YoucanseehowthisprojectionmapvariablesBooksProjectionMapissetfortheBookProviderinListing25-26.YoucanalsoseeinthatlistinghowthisvariablesBooksProjectionMapisthenusedbytheSQLiteQueryBuilderobject.

FulfillingMIME-TypeContractsLet’sstartwiththegetType()methodinListing25-26.ThismethodreturnsaMIMEtypeforagivenURI.Thismethod,likemanyothermethodsofacontentprovider,issensitivetotheincomingURI.Asaresult,thefirstresponsibilityofthegetType()methodistodistinguishthetypeoftheURI.Isitacollectionofbooksorasinglebook?ThecodeusedtheUriMatchertodecipherthisURItype.DependingonthisURI,the

BookTableMetaDataclasshasdefinedtheMIME-typeconstantstoreturnforeachURI.

ImplementingtheQueryMethodLiketheothermethods,thequerymethodusesUriMatchertoidentifytheURItype.IftheURItypeisasingle-itemtype,themethodretrievesthebookIDfromtheincomingURIbylookingatthefirstsegmentreturnedbygetPathSegments().

ThequerymethodthenusestheprojectionsthatwecreatedupfrontinListing25-26toidentifythereturncolumns.Intheend,queryreturnsthecursortothecaller.Throughoutthisprocess,thequerymethodusestheSQLiteQueryBuilderobjecttoformulateandexecutethequery.

WhilereadingthedataonecanconstraintherowsreturnedeitherusingtheURIorthroughexplicitwhereclauseargumentspassedtothequerymethodasinputs.IntheBookProviderimplementationofListing25-26weusedtheapproachofusingtheURIsegmentstoretrievethebookIDtoreturnthevaluesforjustthatbook.

InsteadyoucanusetheselectionparameterandtheselectionArgsparameterofthequery()methodtoexplicitlypassthewhereclausearguments.TheseargumentsworkjustliketheSQLiteDatabase.query()argumentsinListing25-12,where“?”areusedasplaceholdersforthevaluespassedintheselectionArgsarray.

ImplementingtheInsertMethodTheinsertmethodinacontentproviderisresponsibleforinsertingarecordintotheunderlyingdatabaseandthenreturningaURIthatpointtothenewlycreatedrecord.

Liketheothermethods,insertusesUriMatchertoidentifytheURItype.ThecodefirstcheckswhethertheURIindicatesthepropercollection-typeURI.Ifnot,thecodethrowsanexception.

Thecodethenvalidatestheoptionalandmandatorycolumnparameters.Thecodecansubstitutedefaultvaluesforsomecolumnsiftheyaremissing.

Next,thecodeusesanSQLiteDatabaseobjecttoinsertthenewrecordandreturnsthenewlyinsertedID.Intheend,thecodeconstructsthenewURIusingthereturnedIDfromthedatabase.

ImplementingtheUpdateMethodTheupdatemethodinacontentproviderisresponsibleforupdatingarecord(orrecords)basedonthecolumnvaluespassedin,aswellasthewhereclausethatispassedin.Theupdatemethodthenreturnsthenumberofrowsupdatedintheprocess.

Liketheothermethods,updateusesUriMatchertoidentifytheURItype.IftheURItypeisacollection,thewhereclauseispassedthroughsoitcanaffectasmanyrecordsas

possible.IftheURItypeisasingle-recordtype,thenthebookIDisextractedfromtheURIandspecifiedasanadditionalwhereclause.Intheend,thecodereturnsthenumberofrecordsupdated.AlsonoticehowthisnotifyChangemethodenablesyoutoannouncetotheworldthatthedataatthatURIhaschanged.Potentially,youcandothesameintheinsertmethodbysayingthatthecollectionofbooksdataatURI“…/books”haschangedwhenarecordisinserted.

ImplementingtheDeleteMethodThedeletemethodinacontentproviderisresponsiblefordeletingarecord(orrecords)basedonthewhereclausethatispassedin.Thedeletemethodthenreturnsthenumberofrowsdeletedintheprocess.

Liketheothermethods,deleteusesUriMatchertoidentifytheURItype.IftheURItypeisacollectiontype,thewhereclauseispassedthroughsoyoucandeleteasmanyrecordsaspossible.Ifthewhereclauseisnull,allrecordswillbedeleted.IftheURItypeisasingle-recordtype,thebookIDisextractedfromtheURIandspecifiedasanadditionalwhereclause.Intheend,thecodereturnsthenumberofrecordsdeleted.

RegisteringtheProviderFinally,youmustregisterthecontentproviderintheAndroid.Manifest.xmlfileusingthetagstructureinListing25-27.Aproviderisacomponentandhenceasiblingoftheothercomponentssuchasanactivityandareceiver.SoitisasiblingnodetootheractivitiesintheAndroidmanifestfile.

Listing25-27.RegisteringaProvider

<providerandroid:name=".BookProvider"

android:authorities="com.androidbook.provider.BookProvider"/>

ExercisingtheBookProviderNowthatwehaveabookprovider,wearegoingtoshowyousamplecodetoexercisethatprovider.Thesamplecodeincludesaddingabook,removingabook,gettingacountofthebooks,andfinallydisplayingallthebooks.

Keepinmindthatthesearecodeextractsfromthesampleprojectandwillnotcompile,becausetheyrequireadditionaldependencyfiles.However,wefeelthissamplecodeissufficientindemonstratingtheconceptswehaveexplored.

Attheendofthischapter,wehaveincludedalinktothedownloadablesampleproject,whichyoucanuseinyourEclipseenvironmenttocompileandtest.

AddingaBook

ThecodeinListing25-28insertsanewbookintothebookdatabase.

Listing25-28.ExercisingaProviderInsert

//Filereferenceinproject:ProviderTester.JavapublicvoidaddBook(Contextcontext){Stringtag="ExerciseBookProvider";Log.d(tag,"Addingabook");ContentValuescv=newContentValues();cv.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME,"book1");cv.put(BookProviderMetaData.BookTableMetaData.BOOK_ISBN,"isbn-1");cv.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR,"author-1");

ContentResolvercr=context.getContentResolver();Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Log.d(tag,"bookinserturi:"+uri);UriinsertedUri=cr.insert(uri,cv);Log.d(tag,"inserteduri:"+insertedUri);}

RemovingaBookThecodeinListing25-29deletesthelastrecordfromthebookdatabase.

Listing25-29.ExercisingaProviderDelete

//Filereferenceinproject:ProviderTester.JavapublicvoidremoveBook(){intfirstBookId=this.getFirstBookId();if(firstBookId==-1)thrownewSQLException("Bookidislessthan0");ContentResolvercr=this.mContext.getContentResolver();Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;UridelUri=Uri.withAppendedPath(uri,Integer.toString(firstBookId));reportString("DelUri:"+delUri);cr.delete(delUri,null,null);this.reportString("NumberofBooksafterthedelete:"+getCount());}

privateintgetFirstBookId(){Uriuri

=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Activitya=(Activity)this.mContext;Cursorc=null;try{c=a.getContentResolver().query(uri,null,//projectionnull,//selectionstringnull,//selectionargsarrayofstringsnull);//sortorderintnumberOfRecords=c.getCount();if(numberOfRecords==0){return-1;}c.moveToFirst();intid=c.getInt(1);//idcolumnreturnid;}finally{if(c!=null)c.close();}}

DisplayingtheListofBooksThecodeinListing25-30retrievesalltherecordsinthebookdatabase.

Listing25-30.DisplayingaListofBooks

//Filereferenceinproject:ProviderTester.JavapublicvoidshowBooks(){Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Activitya=(Activity)this.mContext;Cursorc=null;try{c=a.getContentResolver().query(uri,null,//projectionnull,//selectionstringnull,//selectionargsarrayofstringsnull);//sortorderintiid=c.getColumnIndex(BookProviderMetaData.BookTableMetaData._ID);intiname=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_NAME);intiisbn=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_ISBN);intiauthor

=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR);

//ReportyourindexesLog.d(tag,"name,isbn,author:"+iname+iisbn+iauthor);

//walkthroughtherowsbasedonindexesfor(c.moveToFirst();!c.isAfterLast();c.moveToNext()){//GathervaluesStringid=c.getString(iid);Stringname=c.getString(iname);Stringisbn=c.getString(iisbn);Stringauthor=c.getString(iauthor);

//ReportorlogtherowStringBuffercbuf=newStringBuffer(id);cbuf.append(",").append(name);cbuf.append(",").append(isbn);cbuf.append(",").append(author);Log.d(tag,cbuf.toString());}

//ReporthowmanyrowshavebeenreadintnumberOfRecords=c.getCount();Log.d(tag,"NumofRecords:"+numberOfRecords);}finally{if(c!=null)c.close();}}

NoticethatthemethodofretrievingthebooksfromacontentproviderisverysimilartoretrievingdatafromanSQLitedatabase.InListing25-30wehaveusedthequery()methodfromaContentResolverobject.Afterusingthecursorobjectwehaveclosedthecursor.

InsteadifyouwerepassingthiscursorobjecttoaUIcomponentthatisintheActivitythenthiscursorobjectneedstobemanagedastheactivityfollowsitslifecycle.PriortoHoneycomb,therewasamethodcalledmanagedQuery()ontheActivitytodothisautomatically,whichhassincebeendeprecatedinfavorofCursorLoader.

WhenaqueryisthusmanagedthroughmanagedQuery(),theactivitycancallmethodsonthecursortoplaceitintoproperstate.Forexample,theactivitywillcalldeactivate()onthecursorwhenitisstoppedandlatercallsrequery()whenitisstarted.Thecursorwillbeclosedwhentheactivityisdestroyed.YoucanchoosetocallstopManagingCursor()onthatcursorifyouwanttocontrolthebehaviorofthecursoryourself.Becausetheactivityclosesthecursor,don’tcloseamanagedcursor.Ifyourintentionistoreadalltherowsonetimeandclosethecursor,thenusethequery()

methodoftheContentResolverasopposedtotheActivity.managedQuery()methodandexplicitlyclosethecursor.

SinceHoneycomb,thecursorreadsarewrappedintoamoregeneralapproachcalled“Loaders,”whichallowyoutoreaddatainanasynchronousthreadthroughcallbacksexposedtofragmentsoractivities.Thisistherecommendedandpreferredmethod.Wewillcoverthisapproachinthenextchapter,Chapter26,onLoaders.

YouhaveseenhowwehaveusedupdateAPIsonacontentproviders.Theseupdateoperationscanbeinefficientifdoneonebyonethroughacontentprovider.InChapter27wewillcoverhowtheseindividualupdateoperationscanbesentasabatchtoacontentproviderforefficiencyreasons.

ResourcesHerearesomeadditionalAndroidresourcesthatcanhelpyouwiththetopicscoveredinthischapter:

http://developer.android.com/guide/topics/data/data-storage.html:VariousdatastorageoptionsfromAndroiddocumentation.

http://www.androidbook.com/item/4437:AsummaryofoptionsforpersistenceonAndroid.

http://www.androidbook.com/item/4876:ExploringtoolsandtechniquesfordirectSQLstorageonAndroid.ThisincludesresearchonO/Rmappingtoolsaswell.

http://www.androidbook.com/item/4877:StoringdatainthecloudthroughParseforAndroid.

http://www.androidbook.com/item/4440:UsingGSON/JSONformobileappstorage.

http://www.androidbook.com/item/4438:Usingsharedpreferencesforapplicationstatemanagement.

http://developer.android.com/guide/topics/providers/content-providers.html:Androiddocumentationoncontentproviders.

http://developer.android.com/reference/android/content/ContentProvider.htmlAPIdescriptionforaContentProvider,whereyoucanlearnaboutContentProvidercontracts.

http://developer.android.com/reference/android/content/UriMatcher.htmlInformationthatisusefulforunderstandingUriMatcher.

http://developer.android.com/reference/android/database/Cursor.htmlInformationthathelpsyoureaddatafromacontentproviderora

databasedirectly.

http://developer.android.com/guide/components/loaders.htmlDeveloper’sguideforLoaders.

http://developer.android.com/reference/android/app/Activity.html#startManagingCursor(android.database.CursorAPIdocumentationofwhatamanagedcursoris.

http://www.sqlite.org/sqlite.html:HomepageofSQLite,whereyoucanlearnmoreaboutSQLiteanddownloadtoolsthatyoucanusetoworkwithSQLitedatabases.

http://androidbook.com/proandroid5/projects:DownloadabletestprojectforthischapterisaccessiblefromthisURL.ThenameofthezipfileisProAndroid5_Ch25_TestProvider.zip.

SummaryThischapterhascoveredalotofaspectsaboutavitalneedofyourapplications:persistence.WehavegivenyouaplethoraofoptionsavailableinAndroidforpersistenceandhowtochooseanappropriateoption.WehavecoveredhowtouseSQLiteforinternalpersistenceneedsinsignificantdetail.Wehaveshownyouanindustrial-strengthAPIpatternforpersistenceusingSQLitewhichcanbeextendedtoanypersistenceimplementation.Importantly,thispatternshowedyouhowtoexternalizetransactionstokeepyourpersistencecodesimple.Wehavethencoveredwhatcontentprovidersare,thenatureofcontentURIs,MIMEtypes,howtouseSQLitetoconstructprovidersthatrespondtoURIs,howtowriteanewcontentprovider,andhowtoaccessanexistingcontentprovider.

Chapter26

UnderstandingLoadersThischapterlooksatloadingdatafromdatasourcesthroughtherecommendedmechanismofLoaders.TheAPIofLoadersisdesignedtodealwithtwoissueswithloadingdatabyactivitiesandfragments.

Thefirstisthenon-deterministicnatureofactivitieswhereanactivitycanbehiddenpartiallyorfully,restartedduetodevicerotation,orremovedfrommemorywheninbackgroundduetolow-memoryconditions.Theseeventsarecalledactivitylifecycleevents.Anycodethatretrievesdatamustworkinharmonywiththeactivitylifecycleevents.PriortotheintroductionofLoadersin3.0(API11),thiswashandledthroughManagedCursors.ThismechanismisnowdiscontinuedinfavorofLoaders.

Thesecondissuewithloadingdatainactivitiesandfragmentsisthatdataaccesscouldtakelongeronthemainthreadresultinginapplication-not-responding(ANR)messages.Loaderssolvethisbydoingtheworkonaworkerthreadandprovidingcallbackstotheactivitiesandfragmentstorespondtotheasynchronousnatureofdatafetch.

UnderstandingtheArchitectureofLoadersLoadersmakeiteasytoasynchronouslyloaddatainanactivityorafragment.Multipleloaders,eachwithitsownsetofdata,canbeassociatedwithanactivityorafragment.Loadersalsomonitorthesourceoftheirdataanddelivernewresultswhenthedatacontentchanges.Loadersautomaticallyreconnecttothepreviouslyretrieveddatastructure,likeacursor,whenbeingre-createdafteraconfigurationchange.Asthepreviouscursorisnotdestroyed,dataisnotrequeried.

Whenwetalkaboutloadersinthischapterallaspectsofloadersapplytobothactivitiesandfragmentsunlessweindicateotherwisefromnowon.

EveryactivityusesasingleLoaderManagerobjecttomanagetheloadersassociatedwiththatactivity.Oncealoaderisregisteredwithaloadermanager,theLoaderManagerwillfacilitatethenecessarycallbackstoa)createandinitializetheLoader,b)readthedatawhentheLoaderfinishesloadingthedata,andc)closetheresourcewhentheloaderisabouttobedestroyedastheactivityisnolongerneeded.TheLoaderManagerishiddenfromyouandyouworkwithitthroughcallbacksandLoaderManagerpublicAPIs.ThecreationoftheLoaderManageriscontrolledbytheactivity.LoaderManagerisalmostlikeanintegralpartoftheactivityitself.

ItistheresponsibilityoftheregisteredLoadertoworkwithitsdatasourceandalsowiththeLoaderManagertoreadthedataandsendtheresultsbacktothe

LoaderManager.TheLoaderManagerwilltheninvokethecallbacksontheactivitythatdataisready.TheLoaderisalsoresponsibleforpausingthedataaccessormonitoringdatachangesorworkingwiththeLoaderManagertounderstandandreacttotheactivitylifecycleevents.

WhileyoucanwritealoaderfromscratchforyourspecificdataneedsbyextendingtheloaderAPI,youtypicallyusetheLoadersthatarealreadyimplementedintheSDK.MostloadersextendtheAsyncTaskLoaderwhichprovidesthebasicabilitytodoitsworkonaworkerthreadfreeingthemainthread.Whentheworkerthreadreturnsdata,theLoaderManagerwillinvokethemaincallbackstotheactivitythatthedataisreadyonthemainthread.

ThemostusedoftheseprebuiltloadersistheCursorLoader.WiththeavailabilityofCursorLoader,usingLoadersbecomesreally,reallytrivialwithafewlinesofcode.ThisisbecauseallthedetailsarehiddenbehindtheLoaderManager,Loader,AsyncTaskLoader,andtheCursorLoader.

ListingBasicLoaderAPIClassesListing26-1liststhekeyclassesinvolvedintheLoaderAPI.

Listing26-1.AndroidLoaderAPIKeyParticipatingClasses

LoaderManagerLoaderManager.LoaderCallbacksLoaderAsyncTaskLoaderCursorLoader

TheAPIsthataremostoftenusedaretheLoaderManager.LoaderCallbacksandtheCursorLoader.However,letusbrieflyintroduceeachoftheseclasses.

ThereisoneLoaderManagerobjectperactivity.ThisistheobjectthatdefinestheprotocolofhowLoadersshouldwork.SoLoaderManageristheorchestratorfortheloadersassociatedwithanactivity.LoaderManager'sinteractionwiththeactivityisthroughtheLoaderManager.LoaderCallbacks.TheseloadercallbacksarewhereyouaregiventhedatabytheLoaderviatheLoaderManagerandexpectedtointeractwiththeactivity.

TheLoaderclassdefinestheprotocolthatmustbeadheredtoifonewantstodesigntheirownloader.AsyncTaskLoaderisoneexamplewhereitimplementstheloaderprotocolinanasynchronousmanneronaworkerthread.ItistypicallytheAsyncTaskLoaderthatisthebaseclasstoimplementmostofyourloaders.CursorLoaderisanimplementationofthisAsyncTaskLoaderthatknowshowtoloadcursorsfromcontentproviders.IfoneisimplementingtheirownloaderitisimportanttounderstandthatallinteractionwiththeloaderfromaLoaderManagerhappensonthemainthread.EventheLoaderManagercallbacksthatareimplementedbytheactivity

takeplaceonthemainthread.

DemonstratingtheLoadersWewillnowshowyouhowtouseLoadersbyimplementingasimpleone-pageapplication(Figure26-1)thatloadscontactsfromthecontactproviderdatabaseonanAndroiddevice.ThisapplicationistypicalofhowonewoulddevelopAndroidactivities.Youcouldevenusethissampleprojectasastarterapplicationtemplate.

Figure26-1.Filteredlistofcontactsloadedthroughloaders

WewanttheactivityinFigure26-1toexhibitthefollowingcharacteristics:1)Itshoulddisplayallthecontactsonthedevice;b)Itshouldretrievedataasynchronously;c)Whiledataisbeingretrieved,theactivityshouldshowaprogressbarviewinplaceofthelistview;d)Onretrievingdatasuccessfully,thecodeshouldreplacetheprogressviewwiththefilled-inlistview;e)Theactivityshouldprovideasearchmechanismtofilterthenecessarycontacts;f)Whenthedeviceisrotated,itshouldshowthecontactsagainwithoutmakingarequerytothecontactscontentprovider;g)Thecodeshouldallowustoseetheorderofcallbacksalongwiththeactivitylifecyclecallbacks.

Wewillfirstpresentthesourcecodefortheactivityandthenexplaineachsection.BytheendofthechapteryouwillhaveaclearunderstandingofhowLoadersworkandhowto

usetheminyourcode.Withthat,Listing26-2showsthecodefortheactivityofFigure26-1.PleasenotethatthecodeinListing26-2reliesonanumberofresourcesthatarepresentedhere.SomeofthesestringresourcesyoucanseeinFigure26-1,butforothersandthecodethatisnotincludedhere,pleaseseethedownloadableproject.Asalways,thecodepresentedhereissufficientforthetopicathand.Listing26-2.AnActivityLoadingDatawithLoaders

publicclassTestLoadersActivity

extendsMonitoredListActivity//verysimpleclasstologactivitycallbacksimplementsLoaderManager.LoaderCallbacks<Cursor>//LoaderManagercallbacks,OnQueryTextListener//Searchtextcallbacktofiltercontacts{privatestaticfinalStringtag="TestLoadersActivity";

//Adapterfordisplayingthelist'sdata

//InitializedtonullcursorinonCreateandsetonthelist//Useitinlatercallbackstoswapcursor//ThisisreinitializedtonullcursorwhenrotationoccursSimpleCursorAdaptermAdapter;

//SearchfilterworkingwithOnQueryTextListener

StringmCurFilter;

//Contactscolumnsthatwewillretrieve

staticfinalString[]PROJECTION=newString[]{ContactsContract.Data._ID,ContactsContract.Data.DISPLAY_NAME};

//selectcriteriaforthecontactsURI

staticfinalStringSELECTION="(("+ContactsContract.Data.DISPLAY_NAME+"NOTNULL)AND("+ContactsContract.Data.DISPLAY_NAME+"!=''))";

publicTestLoadersActivity(){super(tag);}@OverrideprotectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

this.setContentView(R.layout.test_loaders_activity_layout);

//Initializetheadapterthis.mAdapter=createEmptyAdapter();this.setListAdapter(mAdapter);

//Hidethelistviewandshowtheprogressbar

this.showProgressbar();

//Initializealoaderforanidof0getLoaderManager().initLoader(0,null,this);

}//Createasimplelistadapterwithanullcursor//ThegoodcursorwillcomelaterintheloadercallbackprivateSimpleCursorAdaptercreateEmptyAdapter(){

//Forthecursoradapter,specifywhichcolumnsgointowhichviewsString[]fromColumns={ContactsContract.Data.DISPLAY_NAME};int[]toViews={android.R.id.text1};//TheTextViewinsimple_list_item_1//ReturnthecursorreturnnewSimpleCursorAdapter(this,android.R.layout.simple_list_item_1,null,//cursorfromColumns,toViews);}//ThisisaLoaderManagercallback.ReturnaproperlyconstructedCursorLoader//Thisgetscalledonlyiftheloaderdoesnotpreviouslyexist.//Thismeansthismethodwillnotbecalledonrotationbecause//apreviousloaderwiththisIDisalreadyavailableandinitialized.//Thisalsogetscalledwhentheloaderis"restarted"bycalling//LoaderManager.restartLoader()@OverridepublicLoader<Cursor>onCreateLoader(intid,Bundleargs){

Log.d(tag,"onCreateLoaderforloaderid:"+id);UribaseUri;if(mCurFilter!=null){baseUri=Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));}else{baseUri=Contacts.CONTENT_URI;

}String[]selectionArgs=null;StringsortOrder=null;returnnewCursorLoader(this,baseUri,PROJECTION,SELECTION,selectionArgs,sortOrder);}//ThisisaLoaderManagercallback.Usethedatahere.//Thisgetscalledwhenheloaderfinishes.Calledonthemainthread.//Canbecalledmultipletimesasthedatachangesunderneath.//Alsogetscalledafterrotationwithoutrequeryingthecursor.@OverridepublicvoidonLoadFinished(Loader<Cursor>loader,Cursorcursor){

Log.d(tag,"onLoadFinishedforloaderid:"+loader.getId());Log.d(tag,"Numberofcontactsfound:"+cursor.getCount());this.hideProgressbar();this.mAdapter.swapCursor(cursor);

}//ThisisaLoaderManagercallback.Removeanyreferencestothisdata.//Thisgetscalledwhentheloaderisdestroyedlikewhenactivityisdone.//FYI-thisdoesNOTgetcalledbecauseofloader"restart"//Thiscanbeseenasa"destructor"fortheloader.@OverridepublicvoidonLoaderReset(Loader<Cursor>loader){

Log.d(tag,"onLoaderResetforloaderid:"+loader.getId());this.showProgressbar();this.mAdapter.swapCursor(null);

}@OverridepublicbooleanonCreateOptionsMenu(Menumenu){

//Placeanactionbaritemforsearching.MenuItemitem=menu.add("Search");item.setIcon(android.R.drawable.ic_menu_search);item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);SearchViewsv=newSearchView(this);sv.setOnQueryTextListener(this);item.setActionView(sv);returntrue;}

//ThisisaSearchviewcallback.Restarttheloader.//Thisgetscalledwhenuserentersnewsearchtext.//CallLoaderManager.restartLoadertotriggertheonCreateLoader@OverridepublicbooleanonQueryTextChange(StringnewText){

//Calledwhentheactionbarsearchtexthaschanged.Update//thesearchfilter,andrestarttheloadertodoanewquery//withthisfilter.mCurFilter=!TextUtils.isEmpty(newText)?newText:null;Log.d(tag,"Restartingtheloader");getLoaderManager().restartLoader(0,null,this);

returntrue;}@OverridepublicbooleanonQueryTextSubmit(Stringquery){returntrue;}privatevoidshowProgressbar(){//showprogressbarViewpbar=this.getProgressbar();pbar.setVisibility(View.VISIBLE);//hidelistviewthis.getListView().setVisibility(View.GONE);

findViewById(android.R.id.empty).setVisibility(View.GONE);}privatevoidhideProgressbar(){//showprogressbarViewpbar=this.getProgressbar();pbar.setVisibility(View.GONE);//hidelistviewthis.getListView().setVisibility(View.VISIBLE);}privateViewgetProgressbar(){returnfindViewById(R.id.tla_pbar);}}//eof-class

WewillexplaineachsectionfromListing26-2afterweshowyouthesupportinglayoutfortheactivitycodeinListing26-3.ThislayoutinListing26-3shouldclarifytheviewinFigure26-1(pleasenotethatanumberofresourcesarenotincludedherebutareavailableinthedownloadablefileatapress.com/9781430246800).

Listing26-3.ATypicalListActivityLayoutforLoaders

<?xmlversion="1.0"encoding="utf-8"?><!--**********************************************/res/layout/test_loaders_activity_layout.xml

*correspondingactivity:TestLoadersActicity.java*prefix:tla_(Usedforprefixinguniqueidentifiers)**Use:*Demonstrateloadingacursorusingloaders*Structure:*Headermessage:textview(tla_header)*ListViewHeading,ListView(fixed)*ProgressBar(Toshowwhendataisbeingfetched)*EmptyView(Toshowwhenthelistisempty):ProgressBar*Footer:textview(tla_footer)************************************************--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="2dp"android:paddingRight="2dp"><!--HeaderandMaindocumentationtext-->

<TextViewandroid:id="@+id/tla_header"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:layout_marginTop="4dp"android:padding="8dp"android:text="@string/tla_header"/><!--Headingforthelistview-->

<TextViewandroid:id="@+id/tla_listview_heading"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/gray"android:layout_marginTop="4dp"android:padding="8dp"android:textColor="@color/black"style="@android:style/TextAppearance.Medium"android:text="ListofContacts"/><!--ListViewusedbytheListActivity.Usesastandardidneededby

alistview-->

<!--Fixtheheightofthelistviewinaproductionsetting--><ListViewandroid:id="@android:id/list"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:layout_marginTop="4dp"android:layout_marginBottom="4dp"

android:drawSelectorOnTop="false"/><!--ProgressBar:Toshowandhidetheprogressbarasloadersload

data-->

<ProgressBarandroid:id="@+id/tla_pbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center"android:indeterminate="true"/><!--EmptyList:Usesastandardidneededbyalistview--><TextViewandroid:id="@android:id/empty"android:layout_width="match_parent"android:layout_height="wrap_content"android:visibility="gone"android:layout_marginTop="4dp"android:layout_marginBottom="4dp"android:padding="8dp"android:text="NoContactstoMatchtheCriteria"/><!--Footer:Additionaldocumentationtextandthefooter--><TextViewandroid:id="@+id/tla_footer"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:padding="8dp"android:text="@string/tla_footer"/></LinearLayout>

Let’snowturntounderstandingthecodeinListing26-2.Wewillexplainthiscodethroughaseriesofstepsyouwouldfollowtocodewithloaders.Let’sstartwithstep1,wheretheactivityneedstobeextendedtosupporttheLoaderManagercallbacks.

Step1:PreparingtheActivitytoLoadDataCodenecessarytoloaddatausingloadersisremarkablysmall,becausemostoftheworkisdonebytheCursorLoader.ThefirstthingyouneedtodoistohaveyouractivityextendtheLoaderManager.LoaderCallbacks<Cursor>andimplementthethreeneededmethods:onCreateLoader(),onLoadFinished(),andonLoaderReset().YoucanseehowinListing26-2.Byimplementingthisinterface,youhaveenabledtheactivitytobecomeareceiverfortheLoaderManagereventsthroughthesethreecallbacks.

Step2:InitializingtheLoaderNext,youhavetotelltheactivitythatyouwantaLoaderobjecttoloadthedata.ThisisdonebyregisteringandinitializingaLoaderduringtheonCreate()methodoftheactivityasshowninListing26-4.YoucanalsoseethisintheonCreate()ofListing

26-3aswell,incontextoftheoverallcode.

Listing26-4.InitializingaLoader

intloaderid=0;BundleargBundle=null;LoaderCallbacks<Cursor>loaderCallbacks=this;//thisactivityitselfgetLoaderManager().initLoader(loaderid,argBundle,loaderCallbacks);

Theloaderidargumentisadeveloper-assigneduniquenumberinthecontextofthisactivitytouniquelyidentifythisLoaderfromotherLoadersregisteredwiththisactivity.Notethatintheexamplehere,weareusingonlyoneLoader.

ThesecondargsBundleargumentisusedtopassadditionalargumentstotheonCreateLoader()callbackifneeded.This“bundleofarguments”approachfollowstheusualpatternofdifferedfactoryobjectconstructioninmanyofthemanagedcomponentsinAndroid.Activities,fragments,andloadersareallexamplesofthispattern.

Thethirdargument,loaderCallbacks,isareferencetoanimplementationofthecallbacksrequiredbytheLoaderManager.InListings26-2and26-4,theactivityitselfisplayingthisrole,sowepassthisvariablereferringtotheactivityastheargumentvalue.

OncetheLoaderisregisteredandinitialized,theLoaderManagerwillscheduleacalltotheonCreateLoader()callbackifnecessary.IfacallwaspreviouslymadetotheonCreateLoader()andaloaderobjectisavailablecorrespondingtothisloaderID,thenthemethodonCreateLoader()willnotbetriggered.Asstatedearlier,theexceptionisifthedeveloperoverridesthisbehaviorbycallingLoaderManager.restartLoader().Youwillseethiscallexplainedlaterwhenwetalkedaboutprovidingsearch-basedfilteringcapabilitiestolocateasub-selectionofcontacts.

DelvingintotheStructureofListActivityTheListActivityinFigure26-1isextendingalistactivitywithacontentviewthatisacustomlayoutthroughsetContentView().Thisgivesusalotmoreflexibilitytoplaceothercontrolsinadditiontothelistviewontheactivity.Forexample,wehaveprovidedaheaderview,afooterview,andalsoaprogressbartoshowthatweareintheprocessoffetchingdata.TheonlyconstraintplacedbyaListActivityistonameacontrolwiththereserved@android:id/listviewtoidentifythelistviewthatthelistactivitywouldbeusing.InadditiontothelistviewID,wecanalsoprovideaviewthatthelistactivityusesifthelistisempty.ThisviewisidentifiedbythepredefinedID@android:id/empty.

WorkingwithAsynchronousLoadingofDataLoadersloaddataasynchronously.Becauseofthiswehaveanaddedresponsibilityin

theActivity.onCreate()tohidethelistviewandshowtheprogressindicatoruntilthelistdataisready.Todothis,wehaveaProgressBarcomponentinthelayoutinListing26-3.IntheActivity.onCreate()method,wesettheinitialstateofthelayoutsothatthelistviewishiddenandtheprogressbarisshown.ThisfunctionalityiscodedinthemethodshowProgressbar()inListing26-2.InthesameListing26-2,whenthedataisreadywecallhideProgressbar()tohidetheprogressbarandshowthepopulatedlistvieworanemptylistviewifthereisnodata.

Step3:ImplementingonCreateLoader()TheonCreateLoader()istriggeredbytheinitializationoftheloader.YoucanseethesignatureandimplementationofthismethodinListing26-2.ThismethodconstructsaLoaderobjectforthecorrespondingloaderIDthatispassedinfromtheinitializationstemmingfromthecalltoLoaderManager.initLoader().ThismethodalsoreceivestheargumentbundlethatisprovidedduringtheloaderinitializationforthisloaderID.

Thismethodreturnsaproperlytyped(throughJavagenerics)LoaderobjecttotheLoaderManager.InourcasethistypeisLoader<Cursor>.TheLoaderManagercachestheLoaderobjectandwillreuseit.ThisisusefulbecausewhenthedevicerotatesandtheloaderisreinitializedduetoActivity.onCreate(),LoaderManagerrecognizestheloaderIDandthepresenceofanexistingloader.TheLoaderManagerthenwillnottriggeraduplicatecalltotheonCreateLoader().However,iftheactivityistorealizethattheinputdatatotheloaderhaschanged,theactivitycodecancalltheLoaderManager.restartLoader(),whichwilltriggeracalltotheonLoaderCreate()again.Inthatcase,theLoaderManagerwillfirstdestroytheoldloaderandusethenewonereturnedbytheonLoaderCreate().TheLoaderManagerdoesguaranteethattheolderloaderwillhangarounduntilthenewloaderiscreatedandavailable.

TheonCreateLoader()methodhasfullaccesstothelocalvariablesoftheactivity.Soitcanusetheminanyconceivablemannertoconstructtheneededloaders.IncaseofaCursorLoaderthisconstructionislimitedtotheargumentsavailabletotheconstructoroftheCursorLoader,whichisspecificallybuilttoallowcursorsfromanAndroidcontentprovider.

Inourexample,wehaveusedthecontentURIsprovidedbythecontactscontentprovider.RefertoChapter25,oncontentprovider,forhowtousecontentURIstoretrievecursorsfromcontentproviderdatasources.Itisquitesimple:justindicatetheURIyouwanttogetthedatafrom,supplythefilterstringasanargumentorapathsegmentonthatURIasperthedocumentationavailableforthecontactscontentprovider,specifythecolumnsyouwant,specifythewhereclauseasastring,andconstructtheCursorLoader.

Step4:ImplementingonLoadFinished()

OncetheCursorLoaderisreturnedtotheLoaderManager,theCursorLoaderwillbeinstructedtostartitsworkonaworkerthreadandthemainthreadwillgoontotheUIchores.AtalaterpointthismethodonLoadFinished()iscalledwhenthedataisready.

Thismethodcouldbecalledmultipletimes.Whenthedatafromacontentproviderchanges,astheCursorLoaderhasregistereditselfwiththedatasource,itwillbealerted.CursorLoaderthenwilltriggertheonLoadFinished()again.

IntheonLoadFinished()method,allyouneedtodoistoswapthedatacursorthatisheldbythelistadapter.Thelistadapterwasinitializedoriginallywithanullcursor.Swappingwithapopulatedcursorwillshowthenewdataonthelistview.AswehavehiddenthelistviewinActivity.onCreate(),weneedtoshowthelistviewandhidetheprogressbar.Subsequentlywecangoonswappingthenewcursorsforoldcursorsasdatachanges.Thechangeswillreflectautomaticallyonthelistview.

Whenthedevicerotates,acoupleofthingshappen.TheActivity.onCreate()willbecalledagain.Thiswillsetthelistcursortonullandalsohidethelistview.ThecodeinActivity.onCreate()willalsoinitializetheloaderagain.TheLoaderManagerisprogrammedsothatthisrepeatinitializationisharmless.TheonCreateLoader()willnotbecalled.TheCursorwillnotberequeried.However,theonLoadFinished()getscalledagain,whichiswhatweneededtobreakoutofthisconundrumofinitializingthedatatonullfirstandwonderinghowandwhenitwillbepopulatedifwewerenottorequery.AstheonLoadFinished()getscalledagainonrotation,weareabletoremovetheProgressBar,showthelistview,andswapthevalidcursorfromthenullcursor.Allworks.Yes,itissneakyandround-about,butitworks.

Step5:ImplementingonLoaderReset()Thiscallbackisinvokedwhenapreviouslyregisteredloaderisnolongernecessaryandhencedestroyed.Thiscanhappenwhenanactivityisdestroyedduetoabackbuttonorexplicitlyinstructedtobefinishedbycode.Insuchcases,thiscallbackallowsanopportunitytocloseresourcesorreferencesthatarenolongerneeded.However,itisimportantnottoclosethecursorsastheyaremanagedbythecorrespondingloadersandwillbeclosedforyoubytheframework.ThismightsuggestthattheLoaderManager.restartLoader()mightresultinacalltotheonLoaderReset()astheargumentstotheoldloaderarenolongervalid.Buttestsshowthatthisisnotthecase.ThemethodLoaderManager.restartLoader()willnottriggeracalltothemethodonLoaderReset().TheonLoaderReset()methodisonlycalledwhentheloaderisactivelydestroyedbytheactivitynolongerbeingneeded.YoucanalsoexplicitlyinstructtheLoaderManagertodestroytheloaderbycallingLoaderManager.destroyLoader(loaderid).

UsingSearchwithLoaders

Wewillusesearchinoursampleapplicationtodemonstratethedynamicnatureofloaders.Wehaveattachedasearchviewtothemenu.YoucanseethisinthemethodonCreateOptionsMenu()inListing26-2.HerewehaveattachedaSearchViewtothemenuandprovidedtheactivityasthecallbacktotheSearchViewwhennewtextisprovidedintheSearchView.TheSearchViewcallbackishandledinthemethodonQueryTextchange()ofListing26-2.

IntheonQueryTextChange()method,wetakethenewsearchtextandsetthelocalvariablemCurFilter.WethencallLoaderManager.restartLoader()withthesameargumentsastheLoaderManager.initializeLoader().ThiswilltriggertheonCreateLoader()again,whichwillthenusethemCurFiltertoaltertheparameterstotheCursorLoaderresultinginanewcursor.ThisnewcursorwillreplacetheoldoneintheonLoadFinished()method.

UnderstandingtheOrderofLoaderManagerCallbacksBecauseAndroidprogrammingislargelyevent-based,itisimportanttoknowtheorderofeventcallbacks.TohelpyouunderstandthetimingoftheLoaderManagercallbacks,wehaveriggedthesampleprogramwithlogmessages.Herearesomeresultsshowingtheorderofcallbacks.

Listing26-5showstheorderofcallswhentheactivityisfirstcreated.

Listing26-5.LoaderCallbacksonActivityCreation

Application.onCreate()Activity.onCreate()LoaderManager.LoaderCallbacks.onCreateLoader()Activity.onStart()Activity.onResume()LoaderManager.LoaderCallbacks.onLoadFinished()

Whenthesearchviewfiresanewsearchcriteriathroughitscallback,theorderofcallbacksisasshowninListing26-6.

Listing26-6.LoaderCallbacksonaNewSearchCriteriatriggeredbyRestartLoader

RestartLoader//logmessagefromonQueryTextChangeLoaderManager.LoaderCallbacks.onCreateLoader()LoaderManager.LoaderCallbacks.onLoadFinished()//Notice,nocalltoonLoaderReset()

Listing26-7showstheorderofcallsonconfigurationchange.

Listing26-7.LoaderCallbacksonaConfigurationChange

Application:configchanged

Activity:onCreateActivity.onStart[NocalltotheonCreateLoader]LoaderManager.LoaderCallbacks.onLoadFinished[optionallyifsearchviewhastextinit]SearchView.onQueryChangeTextRestartLoader//justalogmessageLoaderManager.LoaderCallbacks.onCreateLoaderLoaderManager.LoaderCallbacks.onLoadFinished

Listing26-8showstheorderofcallbacksonnavigatingbackornavigatingHomeasthoseactionresultsintheactivityarebeingdestroyed.

Listing26-8.LoaderCallbackswhentheActivityisdestroyed

ActivityonStop()Activity.onDestroy()LoaderManager.LoaderCallbacks.onLoaderReset()//Noticethismethodiscalled

WritingCustomLoadersAsyouhaveseenwiththeCursorLoader,Loadersarespecifictotheirdatasources.Soyoumayneedtowriteyourownloaders.VerylikelyyouwillneedtoderivefromtheAsyncTaskLoaderandspecializeitusingtheprinciplesandcontractslaidoutbytheLoaderprotocol.SeetheSDKdocumentationfortheLoaderclasstogetmoredetails.YoucanalsousetheCursorLoadersourcecodeasaguideinwritingyourownloaders.Thesourcecodeisavailableonlinefrommultiplesources(youcanjustgoogleit)oraspartoftheAndroidsourcedownload.

ResourcesHereareadditionalresourcesforthetopicscoveredinthischapter:

http://www.androidbook.com/item/4890:Researchnotesonloaders.Youwillseeherelinkstoreferences,research,samplecode,images,keyquestions,andongoingnotes.

http://developer.android.com/guide/components/loaders.htmlPrimaryguideforloadersfromAndroid.

http://developer.android.com/guide/components/loaders.html#callbackKeyloaderAPIcallbackstobeimplementedbyanactivityorafragment.

http://developer.android.com/reference/android/content/Loader.htmlLoaderJavaclassAPItounderstandwhatmethodsareavailableona

loaderobjectwhichisoftenpassedtotheloaderAPIcallbacks.

http://developer.android.com/reference/android/app/LoaderManager.htmlLoaderManagerJavaclassAPIwhichisusefultocontroltheloader,suchasinitialing,restarting,orremoving.

http://developer.android.com/reference/android/content/CursorLoader.htmlCursorLoaderJavaclassAPIwhichisusefultoloaddatausingcursors.CursorLoadersarealsopassedasargumentstotheLoaderManagercallbacks.YoucanusethepublicAPIontheCursorLoadertogetitsID,canceltheload,andgettheinputargumentsusedtostartthecursor.

http://developer.android.com/guide/topics/ui/layout/listview.htmlYouwillfindherehowtouseloaderstopopulateandworkwithaListView.

http://developer.android.com/reference/android/provider/ContactsContract.Contacts.htmlContentproviderAPIsavailabletoworkwiththeAndroidcontactsdatabase.

http://developer.android.com/reference/android/app/Activity.html#startManagingCursor(android.database.CursorAPIdocumentationofwhatamanagedcursoris.Thisisusefultoseewhatisdonetoacursorinamanagedenvironment.Thisappliestocursorsmanagedbyloadersaswell.

http://www.androidbook.com/proandroid5/projects:DownloadabletestprojectforthischapterisaccessiblefromthisURL.ThenameofthezipfileisProAndroid5_Ch26_TestLoaders.zip.

SummaryLoadersareessentialtoloaddatafromdatasourcesbothfromatimingperspectiveandalsointermsoftheabilitytodealwiththemanagedlifecycleofactivitiesandfragments.Youhaveseeninthischapterhoweasyitistouseloaderstoloaddatafromcontentproviders.Theresultingcodeisresponsive,abletodealwithconfigurationchanges,andsimple.

Chapter27

ExploringtheContactsAPIInChapters25and26,wecoveredcontentprovidersandtheirclosecousins,theloaders.Welistedthebenefitsofexposingdatathroughcontentproviderabstraction.Inacontentproviderabstraction,dataisexposedasaseriesofURLs.ThesedataURLscanbeusedtoread,query,update,insert,anddelete.TheseURLsandtheircorrespondingcursorsbecometheAPIforthatcontentprovider.

TheContactsAPIisonesuchcontentproviderAPIforworkingwithcontactdata.ContactsinAndroidaremaintainedinadatabaseandexposedthroughacontentproviderwhoseauthorityisrootedat

content://com.android.contacts

TheAndroidSDKdocumentsthevariousURLsandthedatatheyreturnusingasetofJavainterfacesandclassesthatarerootedattheJavapackage

android.provider.ContactsContract

YouwillseenumerousclasseswhoseparentcontextisContactsContract;theseareusefulinquerying,reading,updating,andinsertingcontactsintoandfromthecontentdatabase.TheprimarydocumentationforusingtheContactsAPIisavailableontheAndroidsiteat

https://developer.android.com/guide/topics/providers/contacts-provider.html

TheprimaryAPIentrypointContactsContractisappropriatelynamedbecausethisclassdefinesthecontractbetweentheclientsofthecontactsandtheproviderandprotectorofthecontactsdatabase.

Thischapterexploresthiscontractinafairamountofdetailbutdoesnotcovereverynuance.TheContactsAPIislargeanditstentaclesfar-reaching.However,whenyouapproachtheContactsAPI,itwilltakeafewweeksofresearchtorealizethatitissimpleinitsunderlyingstructure.Thisiswherewewouldliketocontributethemostandexplainthesebasicsinthetimeittakestoreadthischapter.

Android4.0hasextendedtheideaofcontactstoincludeauserprofile,similartoauserprofileinasocialnetwork.Auserprofileisadedicatedcontactthatrepresentstheownerofthedevice.Mostofthegeneralcontact-basedconceptsremainthesame.WewillcoverhowtheContactsAPIisextendedtosupportauserprofile.

UnderstandingAccountsAllcontactsinAndroidworkinthecontextofanaccount.Whatisanaccount?Well,for

example,ifyouhaveyoure-mailthroughGoogle,youaresaidtohaveanaccountwithGoogle.IfyousetupyourselfasauserofFacebook,youaresaidtohaveanaccountwithFacebook.Youwillbeabletosetuptheseaccountsthroughthe“Accounts&sync”Settingsoptiononthedevice.SeetheAndroidUser’sGuidetogetmoredetailsaroundaccountsandhowtosetthemup.

Thecontactsyoumanagearetiedtoaspecificaccount.Anaccountownsitssetofcontacts—oranaccountissaidtobetheparentofacontact.Anaccountisidentifiedbytwostrings:theaccountnameandtheaccounttype.IncaseofGoogle,youraccountnameisyoure-mailusernameatGmailandyouraccounttypeiscom.google.Theaccounttypemustbeuniqueacrossthedevice.Youraccountnameisuniquewithinthataccounttype.Together,anaccounttypeandanaccountnameformanaccount,andonlyoncetheaccountisformedcanasetofcontactsbeinsertedforthataccount.

EnumeratingAccountsTheContactsAPIprimarilydealswithcontactsthatexistinvariousaccounts.ThemechanismofcreatingaccountsisoutsideoftheContactsAPI,soexplainingtheabilitytowriteyourownaccountprovidersandhowtosyncthecontactswithinthoseaccountsisoutsidethescopeofthischapter.Youcanunderstandandbenefitfromthischapterwithoutgoingintothedetailsofhowaccountsgetsetup.However,whenyouwanttoaddacontactoralistofcontacts,youdoneedtoknowwhataccountsexistonthedevice.YoucanusethecodeinListing27-1toenumeratetheaccountsandtheirproperties(theaccountnameandtype).CodeinListing27-1liststheaccountnameandtypegivenacontextvariablesuchasanactivity.

Listing27-1.CodetoDisplayaListofAccounts

publicvoidlistAccounts(Contextctx){AccountManageram=AccountManager.get(ctx);Account[]accounts=am.getAccounts();for(Accountac:accounts){Stringaccount_name=ac.name;Stringaccount_type=ac.type;Log.d("accountInfo",account_name+":"+account_type);}}

TorunthecodeinListing27-1,themanifestfileneedstoaskforpermissionusingthelineinListing27-2.

Listing27-2.PermissiontoReadAccounts

<uses-permissionandroid:name="android.permission.GET_ACCOUNTS"/>

ThecodefromListing27-1willprintsomethinglikethefollowing:

Your-email-at-gmail:com.google

Thisassumesthatyouhaveonlyoneaccount(Google)configured.Ifyouhavemorethanoneaccount,allofthoseaccountswillbelistedinasimilarmanner.

Usingthecontactsapplicationonthedeviceyoucanadd,edit,anddeletecontactstoanyofyourexistingaccounts.

UnderstandingContactsContactsownedbyanaccountarecalledrawcontacts.Arawcontacthasavariablesetofdataelements(forexample,e-mailaddress,phonenumber,name,andpostaladdress).Androidpresentsanaggregatedviewofrawcontactsbylistingonlyonceanyrawcontactsthatseemstomatch.Theseaggregatedcontactsformthesetofcontactsyouseewhenyouopenthecontactsapplication.

Wewillnowexaminehowcontactsandtheirrelateddataarestoredinvarioustables.UnderstandingthesecontacttablesandtheirassociatedviewsiskeytounderstandingtheContactsAPI.

ExaminingtheContactsSQLiteDatabaseOnewaytounderstandandexaminethecontactsdatabasetablesistodownloadthecontactsdatabasefromthedeviceortheemulatorandopenitusingoneoftheSQLiteexplorertools.

Todownloadthecontactsdatabase,usetheFileExplorershowninFigure30-17andnavigatetothefollowingdirectoryonyouremulator:

/data/data/com.android.providers.contacts/databases

Dependingontherelease,thedatabasefilenamemaydifferslightly,butitshouldbecalledcontacts.db,contacts2.db,orsomethingsimilar.In4.0,thecontactsproviderusesasimilarlystructuredbutseparatedatabasefilecalledprofile.dbtoholdthecontactsrelatedtothepersonalprofile.

UnderstandingRawContactsThecontactsyouseeinthecontactsapplicationarecalledaggregatedcontacts.Underneatheachaggregatedcontactliesasetofcontactscalledrawcontacts.Anaggregatedcontactisaviewonasetofsimilarrawcontacts.

Thesetofcontactsbelongingtoanaccountarecalledrawcontacts.Eachrawcontactpointstothedetailsofonepersoninthecontextofthataccount.Thisisincontrasttoanaggregatedcontact,whichcrossesaccountboundariesandbelongstothedeviceasawhole.Thisrelationshipbetweenanaccountanditssetofrawcontactsismaintainedintherawcontactstable.Listing27-3showsthestructureoftherawcontactstableinthe

contactsdatabase.

Listing27-3.RawContactTableDefinition

CREATETABLEraw_contacts(_idINTEGERPRIMARYKEYAUTOINCREMENT,is_restrictedINTEGERDEFAULT0,account_nameSTRINGDEFAULTNULL,account_typeSTRINGDEFAULTNULL,sourceidTEXT,versionINTEGERNOTNULLDEFAULT1,dirtyINTEGERNOTNULLDEFAULT0,deletedINTEGERNOTNULLDEFAULT0,contact_idINTEGERREFERENCEScontacts(_id),aggregation_modeINTEGERNOTNULLDEFAULT0,aggregation_neededINTEGERNOTNULLDEFAULT1,custom_ringtoneTEXTsend_to_voicemailINTEGERNOTNULLDEFAULT0,times_contactedINTEGERNOTNULLDEFAULT0,last_time_contactedINTEGER,starredINTEGERNOTNULLDEFAULT0,display_nameTEXT,display_name_altTEXT,display_name_sourceINTEGERNOTNULLDEFAULT0,phonetic_nameTEXT,phonetic_name_styleTEXT,sort_keyTEXTCOLLATEPHONEBOOK,sort_key_altTEXTCOLLATEPHONEBOOK,name_verifiedINTEGERNOTNULLDEFAULT0,contact_in_visible_groupINTEGERNOTNULLDEFAULT0,sync1TEXT,sync2TEXT,sync3TEXT,sync4TEXT)

AswithmostAndroidtables,therawcontactstablehasthe_IDcolumnthatuniquelyidentifiesarawcontact.Together,thefield’saccount_nameandaccount_typeidentifytheaccounttowhichthiscontact(specifically,therawcontact)belongs.Thesourceidfieldindicateshowthisrawcontactisuniquelyidentifiedintheaccount.

Thefieldcontact_idreferstotheaggregatedcontactthatthisrawcontactisoneof.Anaggregatedcontactpointstooneormoresimilarcontactsthatareessentiallythesamepersonsetupamongmultipleaccounts.

Thefielddisplay_namepointstothedisplaynameofthecontact.Thisisprimarilyaread-onlyfield.Itissetbytriggersbasedonthedatarowsaddedinthedatatable(whichiscoveredinthenextsubsection)forthisrawcontact.

Thesyncfieldsareusedbytheaccounttosynccontactsbetweenthedeviceandtheserver-sideaccountsuchasGooglemail.

AlthoughwehaveusedSQLitetoolstoexplorethesefields,thereismorethanonewayto

discoverthesefields.TherecommendedwayistofollowtheclassdefinitionsasdeclaredintheContactsContractAPI.Toexplorethecolumnsbelongingtoarawcontact,youcanlookattheclassdocumentationforContactsContract.RawContacts.

Thereareadvantagesanddisadvantagestothisapproach.AsignificantadvantageisthatyougettoknowthefieldspublishedandacknowledgedbytheAndroidSDK.Thedatabasecolumnsmaygetaddedordroppedwithoutchangingthepublicinterface.Soifyouusethedatabasecolumnsdirectly,theymayormaynotbethere.Instead,ifyouusethepublicdefinitionsforthesecolumns,youaresafebetweenreleases.

Onedisadvantage,however,isthattheclassdocumentationhasmanyotherconstantsinterspersedwithcolumnnames;wekindofgotlostinfiguringoutwhatwaswhat.ThesenumerousclassdefinitionsgivetheimpressionthattheAPIiscomplexwhen,inreality,80%oftheclassdocumentationfortheContactsAPIistodefineconstantsforthesecolumnsandtheURIstoaccesstheserows.

WhenweexercisetheContactsAPIinlatersections,wewillusetheclass-documentation-basedconstantsinsteadofdirectcolumnnames.However,wefeltthedirectexplorationofthetableswasthequickestwaytohelpyouunderstandtheContactsAPI.

Let’stalknextabouthowthedatarelatingtoacontact(suchase-mailandphonenumber)isstored.

UnderstandingtheContactsDataTableAsseenfromtherawcontacttabledefinition,therawcontact(inananticlimacticsense)isjustanIDindicatingwhataccountitbelongsto.Datapertainingtothecontactisnotintherawcontacttablebutsavedinthedatatable.Eachdataelement,suchase-mailandphonenumber,isstoredasseparaterowsinthedatatabletiedbytherawcontactID.Thedatatable,whosedefinitionisshowninListing27-4,contains16genericcolumnsthatcanstoreanytypeofdataelementsuchasane-mail.

Listing27-4.ContactDataTableDefinition

CREATETABLEdata(_idINTEGERPRIMARYKEYAUTOINCREMENT,package_idINTEGERREFERENCESpackage(_id),mimetype_idINTEGERREFERENCESmimetype(_id)NOTNULL,raw_contact_idINTEGERREFERENCESraw_contacts(_id)NOTNULL,is_primaryINTEGERNOTNULLDEFAULT0,is_super_primaryINTEGERNOTNULLDEFAULT0,data_versionINTEGERNOTNULLDEFAULT0,data1TEXT,data2TEXT,data3TEXT,data4TEXT,data5TEXT,data6TEXT,data7TEXT,data8TEXT,data9TEXT,data10TEXT,data11TEXT,data12TEXT,data13TEXT,data14TEXT,data15TEXT,data_sync1TEXT,data_sync2TEXT,data_sync3TEXT,data_sync4TEXT)

Theraw_contact_idpointstotherawcontacttowhichthisdatarowbelongs.Themimetype_idpointstotheMIMEtypeentryindicatingoneofthetypesidentifiedinthecontactdatatypesinListing27-4.Thecolumnsdata1throughdata15aregenericstring-basedtablesthatcanstoreanythingthatisnecessarybasedontheMIMEtype.Thesyncfieldssupportcontactsyncing.ThetablethatresolvestheMIMEtypeIDsisinListing27-5.

Listing27-5.ContactsMIMETypeLookupTableDefinition

CREATETABLEmimetypes(_idINTEGERPRIMARYKEYAUTOINCREMENT,mimetypeTEXTNOTNULL)

Aswiththerawcontactstable,youcandiscoverthedatatablecolumnsthroughthehelperclassdocumentationforContactsContract.Data.Althoughyoucanfigureoutthecolumnsfromthisclassdefinition,youwillnotknowwhatisstoredineachofthegenericcolumnsfromdata1throughdata15.Toknowthis,youwillneedtoseetheclassdefinitionsforanumberofclassesunderthenamespaceContactsContract.CommonDataKinds.

Someexamplesoftheseclassesfollow:

ContactsContract.CommonDataKinds.Email

ContactsContract.CommonDataKinds.Phone

Infact,youwillseeoneclassforeachofthepredefinedMIMEtypes.Theseclassesareasfollows:Email,Event,GroupMembership,Identity,Im,Nickname,Note,Organization,Phone,Photo,Relation,SipAddress,StructuredName,StructuredPostal,Website.Ultimately,alltheCommonDataKindsclassesdoisindicatewhichgenericdatafields(data1throughdata15)areinuseandwhatfor.

UnderstandingAggregatedContactsUltimately,acontactanditsrelateddataareunambiguouslystoredintherawcontactstableandthedatatable.Anaggregatedcontact,ontheotherhand,isheuristicandcouldbeambiguous.

Whenthereisacontactthatisthesamebetweenmultipleaccounts,youmaywanttoseeonenameinsteadofseeingthesameorsimilarnamerepeatedonceforeveryaccount.Androidaddressesthisbyaggregatingcontactsintoaread-onlyview.Androidstorestheseaggregatedcontactsinatablecalledcontacts.Androidusesanumberoftriggersontherawcontacttableandthedatatabletopopulateorchangethisaggregatedcontacttable.

Beforegoingintoexplainingthelogicbehindaggregation,letusshowyouthecontacttabledefinition(seeListing27-6).

Listing27-6.AggregatedContactTableDefinition

CREATETABLEcontacts(_idINTEGERPRIMARYKEYAUTOINCREMENT,name_raw_contact_idINTEGERREFERENCESraw_contacts(_id),photo_idINTEGERREFERENCESdata(_id),custom_ringtoneTEXT,send_to_voicemailINTEGERNOTNULLDEFAULT0,times_contactedINTEGERNOTNULLDEFAULT0,last_time_contactedINTEGER,starredINTEGERNOTNULLDEFAULT0,in_visible_groupINTEGERNOTNULLDEFAULT1,has_phone_numberINTEGERNOTNULLDEFAULT0,lookupTEXT,status_update_idINTEGERREFERENCESdata(_id),single_is_restrictedINTEGERNOTNULLDEFAULT0)

Noclientdirectlyupdatesthistable.Whenarawcontactisaddedwithitsdetail,Androidsearchesotherrawcontactstoseeiftherearesimilarrawcontacts.Ifthereisone,itwillusetheaggregatedcontactIDofthatrawcontactastheaggregatedcontactIDofthenewrawcontactaswell.Noentryismadeintotheaggregatedcontacttable.Ifnoneisfound,itwillcreateanaggregatedcontactandusethataggregatedcontactasthecontactIDforthatrawcontact.

Androidusesthefollowingalgorithmtodeterminewhichrawcontactsaresimilar:

1. Thetworawcontactshavematchingnames,bothfirstandlast.

2. Thewordsinthenamearethesamebutvaryinorder:“firstlast”or“first,last”or“last,first.”

3. Theshorterversionsofthenamesmatch,suchas“Bob”for“Robert.”

4. Ifoneoftherawcontactshasjustafirstorlastname,thiswilltriggerasearchforotherattributes,suchasphonenumberore-mail,andiftheotherattributesmatch,thecontactwillbeaggregated.

5. Ifoneoftherawcontactsismissingthenamealtogether,thiswillalsotriggerasearchforotherattributesasinstep4.

Becausetheserulesareheuristic,somecontactsmaybeaggregatedunintentionally.Theclientapplicationsneedtoprovideamechanismtoseparatethecontactsinsuchacase.IfyourefertotheAndroidUser’sGuide,youwillseethatthedefaultcontactsapplicationallowsyoutoseparatecontactsthatareunintentionallymerged.

Youcanalsopreventtheaggregationbysettingtheaggregationmodewhenyouinserttherawcontact.TheaggregationmodesareshowninListing27-7.

Listing27-7.AggregationModeConstants

AGGREGATION_MODE_DEFAULT

AGGREGATION_MODE_DISABLEDAGGREGATION_MODE_SUSPENDED

Thefirstoptionisobvious;itishowaggregationworks.

Thesecondoption(disabled)keepsthisrawcontactoutofaggregation.Evenifitisaggregatedalready,AndroidwillpullitoutofaggregationandallocateanewaggregatedcontactIDdedicatedtothisrawcontact.

Thethirdoption(suspended)indicatesthateventhoughthepropertiesofthecontactmaychange,whichwillmakeitinvalidfortheaggregationintothatbatchofcontacts,itshouldbekepttiedtothataggregatedcontact.

Thelastpointbringsoutthevolatiledimensionoftheaggregatedcontact.Sayyouhaveauniquerawcontactwithafirstnameandalastname.Rightnow,itdoesn’tmatchanyotherrawcontact,sothisuniquerawcontactgetsitsownallocationofanaggregatedcontact.TheaggregatedcontactIDwillbestoredintherawcontacttableagainstthatrawcontactrow.

However,yougoandchangethelastnameofthisrawcontact,whichmakesitamatchtoanothersetofcontactsthatareaggregated.Inthatcase,Androidwillremovetherawcontactfromthisaggregatedcontactandmoveittotheotherone,abandoningthissingleaggregatedcontactbyitself.Inthiscase,theIDoftheaggregatedcontactbecomesentirelyabandoned,asitwillnotmatchanythinginthefuturebecauseitisjustanIDwithoutanunderlyingrawcontact.

Soanaggregatedcontactisvolatile.ThereisnotasignificantvaluetoholdontothisaggregatedcontactIDovertime.

Androidofferssomerespitefromthispredicamentbyprovidingafieldcalledlookupintheaggregatedcontactstables.Thislookupfieldisanaggregation(concatenation)oftheaccountandtheuniqueIDofthisrawcontactinthataccountforeachrawcontact.ThisinformationisfurthercodifiedsothatitcanbepassedasaURLparametertoretrievethelatestaggregatedcontactID.AndroidlooksatthelookupkeyandseeswhichunderlyingrawcontactIDsarethereforthislookupkey.Itthenusesabest-fitalgorithmtoreturnasuitable(orperhapsnew)aggregatedcontactID.

Whileweareexplicitlyexaminingthecontactsdatabase,let’sconsideracoupleofcontact-relateddatabaseviewsthatareuseful.

Exploringview_contactsThefirstoftheseviewsistheview_contacts.Althoughthereisatablethatholdstheaggregatedcontacts(contactstable),theAPIdoesn’texposethecontactstabledirectly.Instead,itusesview_contactsasthetargetforreadingtheaggregatedcontacts.WhenyouquerybasedontheURIContactsContract.Contacts.CONTENT_URI,thecolumnsreturnedarebasedonthisviewview_contacts.Thedefinitionofview_contactsviewisshowninListing27-8.

Listing27-8.AViewtoReadAggregatedContacts

CREATEVIEWview_contactsAS

SELECTcontacts._idAS_id,contacts.custom_ringtoneAScustom_ringtone,name_raw_contact.display_name_sourceASdisplay_name_source,name_raw_contact.display_nameASdisplay_name,name_raw_contact.display_name_altASdisplay_name_alt,name_raw_contact.phonetic_nameASphonetic_name,name_raw_contact.phonetic_name_styleASphonetic_name_style,name_raw_contact.sort_keyASsort_key,name_raw_contact.sort_key_altASsort_key_alt,name_raw_contact.contact_in_visible_groupASin_visible_group,has_phone_number,lookup,photo_id,contacts.last_time_contactedASlast_time_contacted,contacts.send_to_voicemailASsend_to_voicemail,contacts.starredASstarred,contacts.times_contactedAStimes_contacted,status_update_id

FROMcontactsJOINraw_contactsASname_raw_contactON(name_raw_contact_id=name_raw_contact._id)

Noticethattheview_contactsviewcombinesthecontactstablewiththerawcontacttablebasedontheaggregatedcontactID.

Exploringcontact_entities_viewAnotherusefulviewisthecontact_entities_viewthatcombinestherawcontactstablewiththedatatable.Thisviewallowsustoretrieveallthedataelementsofagivenrawcontactonetime,oreventhedataelementsofmultiplerawcontactsbelongingtothesameaggregatedcontact.Listing27-9presentsthedefinitionofthisviewbasedoncontactentities.

Listing27-9.ContactEntitiesView

CREATEVIEWcontact_entities_viewAS

SELECTraw_contacts.account_nameASaccount_name,raw_contacts.account_typeASaccount_type,raw_contacts.sourceidASsourceid,raw_contacts.versionASversion,

raw_contacts.dirtyASdirty,raw_contacts.deletedASdeleted,raw_contacts.name_verifiedASname_verified,packageASres_package,contact_id,raw_contacts.sync1ASsync1,raw_contacts.sync2ASsync2,raw_contacts.sync3ASsync3,raw_contacts.sync4ASsync4,mimetype,data1,data2,data3,data4,data5,data6,data7,data8,data9,data10,data11,data12,data13,data14,data15,data_sync1,data_sync2,data_sync3,data_sync4,

raw_contacts._idAS_id,

is_primary,is_super_primary,data_version,data._idASdata_id,raw_contacts.starredASstarred,raw_contacts.is_restrictedASis_restricted,groups.sourceidASgroup_sourceid

FROMraw_contactsLEFTOUTERJOINdataON(data.raw_contact_id=raw_contacts._id)LEFTOUTERJOINpackagesON(data.package_id=packages._id)LEFTOUTERJOINmimetypesON(data.mimetype_id=mimetypes._id)LEFTOUTERJOINgroupsON(mimetypes.mimetype='vnd.android.cursor.item/group_membership'ANDgroups._id=data.data1)

TheURIsneededtoaccessthisviewareavailableintheclassContactsContract.RawContactsEntity.

WorkingwiththeContactsAPISofar,wehaveexploredthebasicideabehindtheContactsAPIbyexploringitstablesandviews.Wewillnowpresentanumberofcodesnippetsthatcanbeusedtoexplorecontacts.Thesesnippetsaretakenfromthesampleapplicationthatisdevelopedtosupportthischapter.Althoughthesnippetsaretakenfromthesampleapplication,theyaresufficienttoaidtheunderstandingofhowtheContactsAPIwork.YoucandownloadthefullsampleprogramusingtheprojectdownloadURLattheendofthischapter.

ExploringAccountsWewillstartourexercisebywritingaprogramthatcanprintoutthelistofaccounts.Wehavealreadygiventhecodesnippetsnecessarytogetalistofaccounts.ConsidertheclassAccountsFunctionTesterinListing27-10.

Listing27-10.AccountsFunctionTesterThatPrintsAvailableAccounts

//Javaclass:AccountsFunctionTester.java//Menutoinvokethis:Accounts//BaseTesterisasupportingbaseclassholdingtheparentactivity//andsomereusedcommonvariables.Seethesourcecodeifyouaremorecurious.publicclassAccountsFunctionTesterextendsBaseTester{privatestaticStringtag="tc>";

//IReportBackisasimplelogginginterfacethatwriteslogmessages//tothemainactivityandalsotothelog.publicAccountsFunctionTester(Contextctx,IReportBacktarget){super(ctx,target);}publicvoidtestAccounts(){AccountManageram=AccountManager.get(this.mContext);Account[]accounts=am.getAccounts();for(Accountac:accounts){Stringacname=ac.name;Stringactype=ac.type;this.mReportTo.reportBack(tag,acname+":"+actype);}}}

NoteAswepresentandexploretheJavacodenecessarytoworkwithcontacts,youwillseethreevariablesrepeatedlyusedinthepresentedsourcecode:

mContext:Avariablepointingtoanactivity

mReportTo:Avariableimplementingalogginginterface(IReportBack—youcanseethisJavafileinthedownloadableproject)thatcanbeusedtologmessagestothetestactivitythatisusedforthischapter

Utils:Astaticclassthatencapsulatesverysimpleutilitymethods

WehavechosennottolisttheseclassesherebecausetheywilldistractyoufromunderstandingthecorefunctionalityoftheContactsAPI.Youcanexaminetheseclassesinthedownloadableproject.

Allthecodeinthischapterusesanunmanagedqueryagainstthecontentprovider.ThisisdonebycallingActivity.getContentResolver().query().Thisisbecausewemerelyreadthedataandprintouttheresultsrightaway.IfyourgoalinsteadistouseUI(throughactivitiesorfragments)asatargettodisplayyourcontactsthenreadChapter27onloaders.Loadersshowtherightwaytodisplaycursorsfromanycontentprovider.

Whenyourunthesampleprogramthatyoucandownloadforthischapter,youwillseeamainactivitythatappearswithanumberofmenuoptions.Themenuoption“Accounts”willprintthelistofaccountsavailableonthedevice.

ExploringAggregatedContactsLet’sseehowwecanexploreaggregatedcontactsthroughcodesnippets.Toreadcontacts,youneedtorequestthefollowingpermissioninthemanifestfile:

android.permission.READ_CONTACTS

Asthefunctionalitywearetestingdealswithcontentproviders,URIs,andcursors,let’slookatsomeusefulcodesnippetspresentedinListing27-11.(Thesecodesnippetsareavailableeitherinutils.javaorinsomeofthebaseclassesderivedfromBaseTesterinthechapter’sdownloadableproject.)

Listing27-11.GettingaCursorGivenaURIandawhereClause

//Utils.java//RetrieveacolumnfromacursorpublicstaticStringgetColumnValue(Cursorcc,Stringcname){inti=cc.getColumnIndex(cname);returncc.getString(i);}//SeewhatcolumnsarethereinacursorprotectedstaticStringgetCursorColumnNames(Cursorc){intcount=c.getColumnCount();StringBuffercnamesBuffer=newStringBuffer();for(inti=0;i<count;i++){Stringcname=c.getColumnName(i);cnamesBuffer.append(cname).append(';');}returncnamesBuffer.toString();}

//FromURIFunctionTester.java,baseclassofsomeoftheothertesters

//GivenaURIandawhereclausereturnacursorprotectedCursorgetACursor(Uriuri,Stringclause){Activitya=(Activity)this.mContext;//mContextcoming

fromBaseTesterreturna.getContentResolver().query(uri,null,clause,null,null);}

Inthissection,weareprimarilyexploringthecursorreturnedbyaggregatedcontactURIs.Eachrowreturnedbytheresultingcontactcursorwillhaveanumberoffields.Forourexample,wearenotinterestedinallthefieldsbutonlyafew.YoucanabstractthisoutintoanotherclasscalledanAggregatedContact.Listing27-12showsthisclass.

Listing27-12.AnObjectDefinitionforaFewFieldsofanAggregatedContact

//AggregatedContact.javapublicclassAggregatedContact{publicStringid;publicStringlookupUri;publicStringlookupKey;publicStringdisplayName;publicvoidfillinFrom(Cursorc){id=Utils.getColumnValue(c,"_ID");lookupKey=Utils.getColumnValue(c,ContactsContract.Contacts.LOOKUP_KEY);lookupUri=ContactsContract.Contacts.CONTENT_LOOKUP_URI+"/"+lookupKey;displayName=Utils.getColumnValue(c,ContactsContract.Contacts.DISPLAY_NAME);}}

InListing27-12weusethecursortoloadupthefieldsthatweareinterestedin.

GettingtheAggregatedContactsCursorListing27-13showshowtoretrieveacursorthatisacollectionofaggregatedcontacts.

Listing27-13.GettingaCursorforAllAggregatedContacts

//Getacursorofallcontacts.Specifythewhereclauseasnulltoindicateallrows.//Javaclass:AggregatedContactFunctionTester.java//Menuitemtoinvoke:ContactsCursorprivateCursorgetContacts(){Uriuri=ContactsContract.Contacts.CONTENT_URI;//SpecifyascendingordescendingwaytosortnamesStringsortOrder=ContactsContract.Contacts.DISPLAY_NAME+"COLLATELOCALIZEDASC";Activitya=(Activity)this.mContext;//Localvariablepointingtoanactivity

returna.getContentResolver().query(uri,null,null,null,sortOrder);}

TheURIusedtoreadallthecontactsisContactsContract.Contacts.CONTENT_URI.YoucanpassthisURItothequery()functiontoretrieveacursor.Youcanpassnullasthecolumnprojectiontoreceiveallcolumns.Althoughthisisnotrecommendedinpractice,inourcase,itmakessensebecausewewanttoknowaboutallthecolumnsitreturns.Wehavealsousedthedisplaynameofthecontactasthesortorder.Noticehow,again,wehaveusedContactContract.Contactstogetthecolumnnameforthecontactdisplayname.IfyouweretoprintthefieldnamesfromthiscursoryouwillseethereturnedfieldsasthoseshowninListing27-14.Dependingonthereleasetheordermaybedifferentandmorecolumnsmaybeadded.Itisagoodpracticetoexplicitlyspecifyaprojectiontothequeryclause;thatwayyourcodewillworkacrossreleases.

Listing27-14.AggregatedContactsContentURICursorColumns

times_contacted;contact_status;custom_ringtone;has_phone_number;phonetic_name;phonetic_name_style;contact_status_label;lookup;contact_status_icon;last_time_contacted;display_name;sort_key_alt;in_visible_group;_id;starred;sort_key;display_name_alt;contact_presence;display_name_source;contact_status_res_package;contact_status_ts;photo_id;send_to_voicemail;

ReadingAggregatedContactDetailsNowthatwe’veexploredthecolumnsavailablewiththecontactscontentURI,let’spickafewcolumnsandseewhatcontactrowsareavailable.Weareinterestedinthefollowingcolumnsfromacontactcursor:displayname,lookupkey,andlookupURI.WeareconsideringthesefieldsbecausewewanttoseewhatthelookupkeyandlookupkeyURIlooklikebasedonwhatiscoveredinthetheorypartofthischapter.Specifically,weareinterestedinfiringoffthelookupURItoseewhattypeofacursoritreturns.

ThefunctionlistContacts()inListing27-15getsacontactscursorandprintsthesethreecolumnsforeachrowofthecursor.NotethatthislistingistakenfromaclassthatholdsalocalvariablecalledmContexttoindicatetheactivityandalocalvariablecalledmReportTotobeabletologanymessagestotheactivity.

Listing27-15.PrintingtheLookupKeysforanAggregatedContact

//Javaclass:AggregatedContactFunctionTester.java//Menuitemtoinvoke:ContactspublicvoidlistContacts(){

Cursorc=null;try{

c=getContacts();inti=c.getColumnCount();this.mReportTo.reportBack(tag,"Numberofcolumns:"+i);this.printLookupKeys(c);}finally{if(c!=null)c.close();}}privatevoidprintLookupKeys(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Stringname=this.getContactName(c);StringlookupKey=this.getLookupKey(c);Stringluri=this.getLookupUri(lookupKey);this.mReportTo.reportBack(tag,name+":"+lookupKey);//logthis.mReportTo.reportBack(tag,name+":"+luri);//log}}privateStringgetLookupKey(Cursorcc){intlookupkeyIndex=cc.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);returncc.getString(lookupkeyIndex);}privateStringgetContactName(Cursorcc){returnUtils.getColumnValue(cc,ContactsContract.Contacts.DISPLAY_NAME);}privateStringgetLookupUri(Stringlookupkey){Stringluri=ContactsContract.Contacts.CONTENT_LOOKUP_URI+"/"+lookupkey;returnluri;}

ExploringtheLookupURI-BasedCursorNowthatweknowhowtoextractlookupURIsforagivenaggregatedcontact,let’sseewhatwecandowithalookupURI.

ThefunctionlistLookupUriColumns()inListing27-16willtakethefirstcontactfromthelistofallcontactsandthenformulatealookupURIforthatcontactandfireofftheURItoseewhatkindofacursoritreturnsbyprintingthecolumnnamesfromthatcursor.

Listing27-16.ExploringtheLookupURICursor

//Class:AggregatedContactFunctionTester.java,Menuitemto

invoke:SingleContactCursorpublicvoidlistLookupUriColumns(){

Cursorc=null;try{c=getContacts();StringfirstContactLookupUri=getFirstLookupUri(c);printLookupUriColumns(firstContactLookupUri);}finally{if(c!=null)c.close();}}privateStringgetFirstLookupUri(Cursorc){

c.moveToFirst();if(c.isAfterLast()){Log.d(tag,"Norowstogetthefirstcontact");returnnull;}StringlookupKey=this.getLookupKey(c);returnthis.getLookupUri(lookupKey);}publicvoidprintLookupUriColumns(Stringlookupuri){

Cursorc=null;try{c=getASingleContact(lookupuri);inti=c.getColumnCount();this.mReportTo.reportBack(tag,"Numberofcolumns:"+i);intj=c.getCount();this.mReportTo.reportBack(tag,"Numberofrows:"+j);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}//Usethelookupuri,retrieveasingleaggregatedcontactprivateCursorgetASingleContact(StringlookupUri){

Activitya=(Activity)this.mContext;returna.getContentResolver().query(Uri.parse(lookupUri),null,null,null,null);}

Asitturnsout,itjustreturnsacursor(asinListing27-14)thatisidenticalincolumnsforthatoftheaggregatedcontactcursorasinListing27-13,exceptthatithasonlyonerowpointingtothecontactforwhichthisisthelookupkey.AlsonoticethatwehaveusedthefollowinglookupURIdefinition:

ContactsContract.Contacts.CONTENT_LOOKUP_URI

YouknowfromthediscussionofthecontactlookupURIsthateachlookupURIrepresentsacollectionofrawcontactidentitiesthathavebeenconcatenated.Thatbeing

thecase,youmighthaveexpectedthelookupURItoreturnaseriesofmatchingrawcontacts.However,thetestinListing27-16isshowingthatitisnotreturningacursorofrawcontactsbutinsteadacursorofcontacts.

NoteAlookupbasedonthecontactlookupURIreturnsanaggregatedcontactandnotarawcontact.

AnothertidbitisthatthelookupprocessfortheaggregatedcontactbasedonthelookupURIisnotlinearorexact.ThismeansAndroidwillnotlookforanexactmatchofthelookupkey.Instead,AndroidparsesthelookupkeyintoitsconstituentrawcontactsandthenfindstheaggregatedcontactIDthatmatchesthemostoftherawcontactrecordsandreturnsthataggregatedcontactrecord.

Oneconsequenceofthisisthatnopublicmechanismisavailabletogofromthelookupkeytoitsconstituentrawcontacts.Instead,youhavetofindthecontactIDforthatlookupkeyandthenfireoffarawcontactURIforthatcontactIDtoretrievethecorrespondingrawcontacts.

Hereisanothercodesnippetthatshowswhatisreturnedfromacursorasanobjectinsteadofasasetofcolumns.ThecodeinListing27-17returnsthefirstaggregatedcontactasanobject.

Listing27-17.CodeTestingAggregatedContacts

//Javaclass:AggregatedContactFunctionTester.javaprotectedAggregatedContactgetFirstContact(){

Cursorc=null;try{c=getContacts();c.moveToFirst();if(c.isAfterLast()){Log.d(tag,"Nocontacts");returnnull;}AggregatedContactfirstcontact=newAggregatedContact();firstcontact.fillinFrom(c);returnfirstcontact;}finally{if(c!=null)c.close();}}

ExploringRawContactsInListing27-18,thefileRawContact.java,capturesafewimportantfieldsfromtherawcontactstablecursor.(Thisfile,likeallothercodesnippetsinthischapter,isavailableinthedownloadableprojectforthischapter.)

Listing27-18.SourcecodeforRawContact.java

//Class:RawContact.javapublicclassRawContact{

publicStringrawContactId;publicStringaggregatedContactId;publicStringaccountName;publicStringaccountType;publicStringdisplayName;

publicvoidfillinFrom(Cursorc){rawContactId=Utils.getColumnValue(c,"_ID");accountName=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);accountType=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);aggregatedContactId=Utils.getColumnValue(c,ContactsContract.RawContacts.CONTACT_ID);displayName=Utils.getColumnValue(c,"display_name");}publicStringtoString(){//..printsthepublicfields.Seethedownloadprojectfordetails}}//eof-class

ShowingtheRawContactsCursorAswiththeaggregatedcontactURIs,let’sfirstexaminethenatureoftherawcontactURIandwhatitreturns.ThesignaturefortherawcontactURIisdefinedasfollows:

ContactsContract.RawContacts.CONTENT_URI

ThefunctionshowRawContactsCursor()inListing27-19printsthecursorcolumnsforarawcontactsURI.

Listing27-19.ExploringtheRawContactsCursor

//Javaclass:RawContactFunctionTester.java;Menuitem:RawContactsCursorpublicvoidshowRawContactsCursor(){

Cursorc=null;try{c=this.getACursor(ContactsContract.RawContacts.CONTENT_URI,null);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}

CodeinListing27-19willshowthattherawcontactcursorhasthefieldsshowninListing27-20(thislistseemtovarysomewhatwitheachdevice).

Listing27-20.RawContactsCursorFields

times_contacted;phonetic_name;phonetic_name_style;contact_id;version;last_time_contacted;aggregation_mode;_id;name_verified;display_name_source;dirty;send_to_voicemail;account_type;custom_ringtone;sync4;sync3;sync2;sync1;deleted;account_name;display_name;sort_key_alt;starred;sort_key;display_name_alt;sourceid;

SeeingtheDataReturnedbyaRawContactsCursorListing27-21showsthemethodshowAllRawContacts(),whichprintsalltherowsintherawcontactscursor.

Listing27-21.DisplayingRawContacts

//Javaclass:RawContactFunctionTester.java;Menuitem:AllRawContactspublicvoidshowAllRawContacts(){

Cursorc=null;try{c=this.getACursor(getRawContactsUri(),null);this.printRawContacts(c);}finally{if(c!=null)c.close();}}privatevoidprintRawContacts(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){RawContactrc=newRawContact();rc.fillinFrom(c);this.mReportTo.reportBack(tag,rc.toString());//log}}

ConstrainingRawContactswithaCorrespondingSetofAggregatedContactsUsingthecolumnsofthecursorinListing27-20,let’sseeifwecanrefineourquerytoretrievethecontactsforagivenaggregatedcontactID.ThecodeinListing27-22willlookupthefirstaggregatedcontactandthenissuearawcontactURIwithawhereclausespecifyingavalueforthecontact_idcolumn.

Listing27-22.GettingRawContactsforanAggregatedContact

//Javaclass:RawContactFunctionTester.java;Menuitem:RawContactspublicvoidshowRawContactsForFirstAggregatedContact(){

AggregatedContactac=getFirstContact();Cursorc=null;try{c=this.getACursor(getRawContactsUri(),getClause(ac.id));this.printRawContacts(c);}finally{if(c!=null)c.close();}}privateStringgetClause(StringcontactId){return"contact_id="+contactId;}

ExploringRawContactDataBecauseadatarowbelongingtoarawcontactcontainsanumberoffields,wehavecreatedaJavaclasscalledContactData.java,showninListing27-23,tocapturearepresentativesetofthecontactdata,andnotallfields.

Listing27-23.SourcecodeforContactData.java

//ContactData.javapublicclassContactData{publicStringrawContactId;publicStringaggregatedContactId;publicStringdataId;publicStringaccountName;publicStringaccountType;publicStringmimetype;publicStringdata1;

publicvoidfillinFrom(Cursorc){rawContactId=Utils.getColumnValue(c,"_ID");accountName=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);accountType=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);aggregatedContactId=

Utils.getColumnValue(c,ContactsContract.RawContacts.CONTACT_ID);mimetype=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.MIMETYPE);data1=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA1);dataId=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA_ID);}

publicStringtoString(){//justaconcatenationoffieldsforlogging}}

AndroidusesaviewcalledaRawContactEntityviewtoretrievedatafromarawcontacttableandthecorrespondingdatatablesasindicatedinthesection“contact_entities_view”inthischapter.TheURItoaccessthisviewisinListing27-24.

Listing27-24.RawEntitiesContentURI

ContactsContract.RawContactsEntity.CONTENT_URI

Let’sseehowthisURIcanbeusedtodiscoverfieldnamesreturnedbythisURI:

//Javaclass:ContactDataFunctionTester.java;Menuitem:ContactEntityCursorpublicvoidshowRawContactsEntityCursor(){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,null);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}

ThecodeinListing27-24printsoutthelistofcolumnsshowninListing27-25.SothecolumnsinListing27-25arethecolumnsthatarereturnedbytherawcontactsentitycursor.Theremaybeadditionalcolumnsdependingonvendor-specificimplementations.

Listing27-25.ContactEntitiesCursorColumns

data_version;contact_id;version;data12;data11;data10;mimetype;res_package;_id;data15;data14;data13;name_verified;is_restricted;is_super_primary;data_sync1;dirty;data_sync3;data_sync2;data_sync4;account_type;data1;sync4;sync3;data4;sync2;data5;sync1;data2;data3;data8;data9;deleted;group_sourceid;data6;data7;account_name;data_id;starred;sourceid;is_primary;

Onceyouknowthissetofcolumns,youcanfiltertheresultsetofthiscursorbyformulatingaproperwhereclause.However,youwanttousetheContactsContractJavaclasstousethedefinitionsforthesecolumnnames.Forexample,inListing27-26weretrievethedataelementspertainingtocontactIDs3,4,and5.

Listing27-26.DisplayingDataElementsfromRawContactsEntity

//Javaclass:ContactDataFunctionTester.java;Menuitem:ContactDatapublicvoidshowRawContactsData(){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,"contact_idin(3,4,5)");this.printRawContactsData(c);}finally{if(c!=null)c.close();}}protectedvoidprintRawContactsData(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){ContactDatadataRecord=newContactData();dataRecord.fillinFrom(c);this.mReportTo.reportBack(tag,dataRecord.toString());}}

CodeinListing27-26willprintsuchthingsasname,e-mailaddress,andMIMEtypeasdefinedintheContactDataobjectinListing27-23.

AddingaContactwithItsDetailsLet’slookatacodesnippettoaddacontactwithname,e-mail,andphonenumber.Towritetocontacts,youneedthefollowingpermissioninthemanifestfile:

android.permission.WRITE_CONTACTS

CodeinListing27-27addsarawcontactfollowedbyaddingtwodatarows(nameandphonenumber)forthatcontact.

Listing27-27.AddingaContact

//Javaclass:AddContactFunctionTester.java;Menuitem:AddContactpublicvoidaddContact(){

longrawContactId=insertRawContact();this.mReportTo.reportBack(tag,"RawcontactId:"+rawContactId);insertName(rawContactId);insertPhoneNumber(rawContactId);showRawContactsDataForRawContact(rawContactId);}privatelonginsertRawContact(){ContentValuescv=newContentValues();cv.put(RawContacts.ACCOUNT_TYPE,"com.google");

cv.put(RawContacts.ACCOUNT_NAME,"--useyourgmailid—");UrirawContactUri=this.mContext.getContentResolver().insert(RawContacts.CONTENT_URI,cv);longrawContactId=ContentUris.parseId(rawContactUri);returnrawContactId;}privatevoidinsertName(longrawContactId){ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,StructuredName.CONTENT_ITEM_TYPE);cv.put(StructuredName.DISPLAY_NAME,"JohnDoe_"+rawContactId);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidinsertPhoneNumber(longrawContactId){ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE);cv.put(Phone.NUMBER,"123123"+rawContactId);cv.put(Phone.TYPE,Phone.TYPE_HOME);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidshowRawContactsDataForRawContact(longrawContactId){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,"_id="+rawContactId);this.printRawContactsData(c);}finally{if(c!=null)c.close();}}

CodeinListing27-27doesthefollowing:

1. Addsanewrawcontactforapredefinedaccountusingtheaccount’snameandtype,representedbythemethodinsertRawContact().NoticehowitusestheURIRawContact.CONTENT_URI.

2. TakestherawcontactIDfromstep1andinsertsanamerecordusingtheinsertName()methodinthedatatable.NoticehowitusestheURIData.CONTENT_URI.

3. TakestherawcontactIDfromstep1andinsertsaphonenumberrecordusingtheinsertPhoneNumber()methodinthedatatable.Beingadatarow,itusesData.CONTENT_URIastheURI.

Listing27-27alsodemonstratesthecolumnaliasesusedininsertingrecords.NoticehowconstantslikePhone.TYPEandPhone.NUMBERpointtothegenericdatatablecolumnnamesdata1anddata2.

ControllingAggregationofContactsClientsthatupdateorinsertcontactsdonotexplicitlychangethecontactstable.Thecontactstableisupdatedbytriggersthatlookintotherawcontacttableandrawcontactdatatable.

Rawcontactsthatgetaddedorchanged,inturn,affecttheaggregatedcontactsinthecontactstable.However,youmaynotwanttoallowtwocontactstobeaggregated.

Youcancontroltheaggregationbehaviorofarawcontactbysettingtheaggregationmodewhenthatcontractiscreated.AsyoucanseefromtherawcontacttablecolumnsinListing27-20,therawcontacttablecontainsafieldcalledaggregation_mode.ValuesfortheaggregationmodeareshowninListing27-7andexplainedinthesection“AggregatedContacts.”

Youcanalsokeeptwocontactsalwaysapartbyinsertingrowsintoatablecalledagg_exceptions.TheURIsneededtoinsertintothistablearedefinedintheJavaclassContactsContract.AggregationExceptions.Thetablestructureofagg_exceptionsisshowninListing27-28.

Listing27-28.AggregateExceptionsTableDefinition

CREATETABLEagg_exceptions(_idINTEGERPRIMARYKEYAUTOINCREMENT,typeINTEGERNOTNULL,raw_contact_id1INTEGERREFERENCESraw_contacts(_id),raw_contact_id2INTEGERREFERENCESraw_contacts(_id))

ThetypecolumninListing27-28holdsoneoftheintegerconstantsinListing27-29.

Listing27-29.AggregationTypesintheAggregationExceptionTable

ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHERContactsContract.AggregationExceptions.TYPE_KEEP_SEPARATEContactsContract.AggregationExceptions.TYPE_AUTOMATIC

TYPE_KEEP_TOGETHERsaysthetworawcontactsshouldneverbebrokenapart.TYPE_KEEP_SEPARATEsaysthattheserawcontactsshouldneverbejoined.TYPE_AUTOMATICsaystousethedefaultalgorithmtoaggregatecontacts.

TheURIyouwillusetoinsert,read,andupdatethistableisdefinedas

ContactsContract.AggregationExceptions.CONTENT_URI

ConstantsforfielddefinitionstoworkwiththistablearealsoavailableintheJavaclassContactsContract.AggregationExceptions.

UnderstandingPersonalProfileApersonalprofile,introducedinAPI14,islikeacontact,exceptthereisonlyonepersonalprofilecontact.Thatisthesingularyou,onyourdevice.

However,asanimplementationdetail,alldatapertainingtothesingularpersonalprofilecontactismaintainedinaseparatedatabasecalledprofile.db.Ourresearchshowsthatthisdatabasehasastructureidenticaltocontacts2.db.Thismeansyoualreadyknowwhatrelevanttablesareavailableandwhatthecolumnsofeachtableare.

Beingasinglecontact,theaggregationisstraightforward.Everyrawcontactthatisaddedtothepersonalprofileisexpectedtobelongtothesingularaggregatedcontact.Ifonedoesn’texist,thenanewaggregatedcontactiscreatedandplacedinthenewrawcontact.Ifoneexists,thatcontactIDisusedastheaggregatedcontactIDfortherawcontact.

TheAndroidSDKusesthesamebaseclassContactsContracttodefinethenecessaryURIstoread/update/delete/addrawcontactstothepersonalprofile.TheseURIsparalleltheircounterpartsbutwiththestring“PROFILE”somewhereinthem.Listing27-30showsafewoftheseURIs.

Listing27-30.Profile-BasedURIsIntroducedin4.0

//RelatestoprofileaggregatedcontactContactsContract.Profile.CONTENT_URI

//RelatestoprofilebasedrawcontactContactsContract.Profile.CONTENT_RAW_CONTACTS_URI

//Relatestoprofilebasedrawcontact+profilebaseddatatableContactsContract.RawContactsEntity.PROFILE_CONTENT_URI

Listing27-30showswehaveseparateURIswhendealingwithaggregatedcontactandarawcontact.However,thereisn’tacorrespondingpersonalprofileURIfortheDatatable.ThesameDataURI,Data.CONTENT_URI,isapplicabletobothregularcontactdataandalsotheprofilecontactdata.

Alsonotethatthesamecontentproviderservestheneedsofboththepersonalprofileandregularcontacts.Internally,thiscontentproviderknowsbasedontherawcontactIDifthedataURIbelongstotheprofiledataortheregularcontactdata.

Let’slooknextatcodesnippetstoreadandaddcontactdatatothepersonalprofile.YouwillneedthepermissionsfromListing27-31toreadfromandwritetotheprofiledata.

Listing27-31.PermissionsReading/WritingProfileData

<uses-permissionandroid:name="android.permission.READ_PROFILE"/><uses-permissionandroid:name="android.permission.WRITE_PROFILE"/>

ReadingProfileRawContactsLet’susethefollowingURItoreadtherawcontactsthatbelongtothepersonalprofile:

ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI

Listing27-32showshowtoreadprofilerawcontactentries.

Listing27-32.ShowingAllProfileRawContacts

//Javaclass:ProfileRawContactFunctionTester.java;Menuitem:PRawContacts//InthedownloadthismethodisnamedshowAllRawContacts//Itisexpandedhereforclarity.publicvoidshowAllRawProfileContacts(){Cursorc=null;try{StringwhereClause=null;c=this.getACursor(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,whereClause);this.printRawContacts(c);}finally{if(c!=null)c.close();}}//InthedownloadthismethodisnamedprintRawContacts//Itisexpandedhereforclarity.privatevoidprintRawProfileContacts(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){RawContactrc=newRawContact();rc.fillinFrom(c);this.mReportTo.reportBack(tag,rc.toString());}}

Noticethatonceweretrievethecursor,thedataitcontainsmatchestheRawContactthatwedefinedearlierforaregularrawcontact.

ReadingProfileContactDataLet’susethefollowingURItoreadthevariousdataelements(suchase-mail,MIMEtype,andsoon)ofrawcontactsthatbelongtothepersonalprofile:

ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI

Noticehowweareusingasimilarviewasinthecaseofregularcontacts.TheRawContactEntityisajoinbetweenrawcontactsandthedatarowsbelongingtothatrawcontact.Wewillseeonerowforeachdataelementsuchasname,e-mail,MIMEtype,andsoon.

Listing27-33showsthecodesnippettoreadprofilerawcontactentries.

Listing27-33.ShowingDataElementsforProfileContacts

//Javaclass:ProfileContactDataFunctionTester.java;Menuitem:allprawcontactspublicvoidshowProfileRawContactsData(){

Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI;StringwhereClause=null;c=this.getACursor(uri,whereClause);this.printProfileRawContactsData(c);}finally{if(c!=null)c.close();}}protectedvoidprintProfileRawContactsData(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){ContactDatadataRecord=newContactData();dataRecord.fillinFrom(c);this.mReportTo.reportBack(tag,dataRecord.toString());}}

Noticethatonceweretrievethecursor,thedataitcontainsmatchestheContactDataobject(Listing27-23)thatwedefinedearlierforaregularrawcontactdataelement.

AddingDatatothePersonalProfileLet’susethefollowingURItoaddarawcontacttoapersonalprofile:

ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI

Wewillalsoaddafewdataelementssuchasaphonenumberandanicknametothatrawcontactsotheyappearinthedetailsofyourpersonalprofileonthedevice.Listing27-34showsthecodesnippet.

Listing27-34.AddingaProfileRawContact

//Javaclass:AddProfileContactFunctionTester.java;Menuitem:allprawcontacts

//Inthesourcecodeyouwon'tseetheword"profile"inthefollowingmethodnames//ItisaddedheretoaddclarityasthewholeclassisnotincludedpublicvoidaddProfileContact(){

longrawContactId=insertProfileRawContact();this.mReportTo.reportBack(tag,"RawcontactId:"+rawContactId);insertProfileNickName(rawContactId);insertProfilePhoneNumber(rawContactId);showProfileRawContactsDataForRawContact(rawContactId);}privatevoidinsertProfileNickName(longrawContactId){

ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);//cv.put(Data.IS_USER_PROFILE,"1");

cv.put(Data.MIMETYPE,CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);cv.put(CommonDataKinds.Nickname.NAME,"PJohnNickname_"+rawContactId);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidinsertProfilePhoneNumber(longrawContactId){

ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE);cv.put(Phone.NUMBER,"P123123"+rawContactId);cv.put(Phone.TYPE,Phone.TYPE_HOME);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatelonginsertProfileRawContact(){

ContentValuescv=newContentValues();cv.put(RawContacts.ACCOUNT_TYPE,"com.google");cv.put(RawContacts.ACCOUNT_NAME,"--useyourgmailid--");UrirawContactUri=this.mContext.getContentResolver()

.insert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,cv);longrawContactId=ContentUris.parseId(rawContactUri);returnrawContactId;}privatevoidshowProfileRawContactsDataForRawContact(longrawContactId){

Cursorc=null;

try{Uriuri=ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI;c=this.getACursor(uri,"_id="+rawContactId);this.printRawContactsData(c);}finally{if(c!=null)c.close();}}

ThecodeinListing27-34parallelsthecodeweusedtoaddaregularcontactanditsdetails(Listing27-27).Althoughwehaveusedaprofile-specificURItoaddarawcontact,wehaveusedthesameData.CONTENT_URItoaddtheindividualdataelements.

Notethefollowingcommented-outcodeinListing27-34:

//cv.put(Data.IS_USER_PROFILE,"1");

BecauseData.CONTENT_URIisnotspecifictotheprofile,howdoestheunderlyingcontentproviderknowwhethertoinsertthisdataintoaregularrawcontactorapersonalprofilerawcontact?WethoughtthatspecifyingacolumncalledIS_USER_PROFILEwouldhelpthecontentprovider.Apparentlynot.Thisnewcolumnisavailableprimarilyforreadpurposes.Yourinsertswillfailifyouspecifythisduringinserts.TheonlyconclusionthenisthatthecontentproviderisrelyingontherawcontactIDtoseewhetherthatrawcontactcamefromprofile.dborcontacts2.db.

RoleofSyncAdaptersSofar,wehavemainlytalkedaboutmanipulatingthecontactsonthedevice.However,accountsandtheircontactsonAndroidworkhandinhandwithserver-basedcontacts.Forexample,ifyouhavecreatedaGoogleaccountonyourAndroidphone,theGoogleaccountwillpullyourGmailcontactsandmakethemavailableonthedevice.TodothissyncingAndroidprovidesasynchronizationframeworkwhichdoesmostofthegroundworkaslongasyouwriteaconformingSyncadapter.Android’ssynchronizationframeworktakescareofnetworkavailability,optionalauthentication,andscheduling.

ImplementingasyncadapterinvolvesimplementingaservicebyextendingtheSDKclassAbstractThreadedSyncAdapteranddoingtheworkinthemethodonPerformSync().WorkinvolvedinthismethodistoloaddatafromserversandupdatethecontactsusingtheContactsAPIthatisdiscussedinthischapter.Then,async-adapterresourcefile(XML)needstobecreatedonthedevicethatwilldescribehowthisserviceistiedtotheaccountthatneedstobesynched.

Outsideofthisbasicunderstanding,duetospacelimitationswehavenotcoveredthesyncingAPIinthiseditionofthebook.AndroidSDKdocumentationhassomedocumentationandsamples.

Synchronizationofcontactshasimpactsondeletingcontactsonthedevice.Whenyou

deleteacontactusingtheaggregatedcontactURI,itwilldeleteallitscorrespondingrawcontactsandthedataelementsofeachofthoserawcontacts.However,Androidwillonlymarkthemasdeletedonthedeviceandexpectsthebackgroundsynctoactuallysyncwiththeserverandthendeletethecontactspermanentlyfromthedevice.Thiscascadingofdeletesalsohappensattherawcontactlevelwherethecorrespondingdataelementsofthatrawcontactaredeleted.

UsingBatchOperationstoOptimizeContentProviderUpdatesWhilecoveringcontentprovidersinChapter26weindicatedthatwewouldcoverthebatchoperationsinthischapter.

Reconsiderhowarawcontactanditsassociateddataelementsarecreatedearlierinthechapter.Noticethemultiplecommandsweneedtosendtothecontactsprovidertoinsertarawcontact.Firstwehavetoinsertrawcontact.ThenusethatIDtoinsertmultipledataelementsbelongingtothatrawcontact.Eachoftheseinsertsisaseparatecommandsenttothecontentproviderindependently.

Therearetwoissueswhenwesendthesemultiplecommandssequentially.Thefirstissueisthatthecontentproviderisnotawarethattheybelongtoasinglecommitunit.Thesecondissueisthatitwilltakelongertoupdatethecontentproviderdatabaseaseachtransactioniscommittedbyitself.

ThesetwoissuesareaddressedbythebatchupdateAPIavailableforanycontentproviderincludingthecontactprovider.

IdeaofBatchingContentProviderUpdatesInthebatchingapproach,eachcontentproviderupdateoperationisencapsulatedinanobjectcalled“ContentProviderOperation”alongwiththeURIandallthenecessarykey/valuepairstoperformthatoperation.Thenyougathertheseoperationsintoalistobject.Youthentellthecontentresolvertosendtheentirebatchorlistofcommandstothecontentprovideratthesametime.Becausethecontentproviderknowsthesecommandsareinabatch,itappliesthetransactionsappropriatelyeitherattheendorsooftenbasedonhints.

Ifanoperationindicatesthatatransactioncanbeappliedattheendofthatoperation,thentheoperationscompletedthusfarwillbecommitted.Thisallowsyoutosub-batchlongupdatesofmanyrowsintosmallersetofsub-rows.Youcanalsoindicateinanoperationthatoneofthecolumnstobeupdatedneedstousethekeyreturnedbyanindexedpreviousoperation.Wewillpresentnowsomesamplecodeshowinghowtheseideaswork.

Listing27-35showsanexampleofcreatingalistobjecttoholdalistofoperations.

Listing27-35.AContainerforContentProviderOperations

ArrayList<ContentProviderOperation>ops=newArrayList<ContentProviderOperation>();

LetusseenowhowtoconstructtheindividualoperationstobeaddedtothatlistinListing27-36.

Listing27-36.BatchingContentProviderOperations

ContentProviderOperation.Builderop=ContentProviderOperation.newInsert(acontentURI);op.withValue(key,value);//...moreoftheseContentProviderOperationop1=op.build();ops.add(op1);

ThekeyclassisContentProviderOperationanditscorrespondingBuilderobject.Intheexamplehereweareusingtheinsertoperation.Fortherestofthemethodsseetheclassreference.OncewehaveabuilderalongwithitsassociatedcontentURI,wetellthebuildertoaddsetofkey/valuepairsthatgoalongwiththatcontentURI.Oncefinishedaddingallthekey/valuepairsweproducetheContentProviderOperationfromthebuilderandaddittothelist.WethenaskthecontentresolvertoapplythebatchofoperationsusingthecodeinListing27-37.

Listing27-37.UsingaContentResolvedtoApplytheBatchofOperations

activity.getContentResolver().applyBatch(contentProviderAuthority,ops);

InListing27-37theargumentcontentProviderAuthorityistheauthoritystringpointingthecontentproviderandtheargumentopsisthelistofoperationsthatshouldbeappliedasabatchtothatcontentprovider.Thisisanexampleofaddingaseriesofupdateoperationsasasingletransaction.Letusseenowhowtoprovidecommithintstosothatcommitoperationscanbedoneonsmallersubsetsfromthegivenbatch.

BatchingCommitsbyYieldingOneproblemwithcommittingalargebatchofcommandsasasingletransactionisthatthisworkcanblockotheroperationsonthedatabase.Tohelpwiththisandalsotohelpwithtoomuchworktobecommittedinasingletransactionyoucaninstructanoperationtoyield.Whenthecontentproviderrecognizestheyieldparameteronanoperationitcommitstheworkdoneandpausestoyieldforotherprocessestorun.

NoticehowinthecodeinListing27-38oneoftheoperationsissettoallowyield.

Listing27-38.UsingYieldinaContentProviderOperation

ContentProviderOperation.BuilderoperationBuilder=ContentProviderOperation.newInsert(acontentURI);operationBuilder.withValue(key,value);//...moreofthesekey/valuepairswhenyouhavethem

ContentProviderOperationop1=operationBuilder.build();

//...AddMoreoperations

//MarkthenextoperationasyieldallowedoperationBuilder=ContentProviderOperation.newInsert(acontentURI);operationBuilder.withValue(key,value);operationBuilder.withYieldAllowed(true);//itisoktocommitContentProviderOperationoperationWithYield=operationBuilder.build();ops.add(operationWithYield);

//...AddMoreoperationsandyieldpointsasneeded

//Finallyapplythelistofoperationsactivity.getContentResolver().apply(contentProviderAuthority,ops);

UsingBackReferencesForoneoftheoperationsaboveyoucanuseabackreferenceasshowninListing27-39.

Listing27-39.UsingaBackReferenceinaContentProviderOperation

//Takethekeycomingoutofop1andadditasthevalueintindexOfTheOperationWhoseKeyYouNeed=0;op.withValueBackReference(mykey,indexOfTheOperationWhoseKeyYouNeed);

CodeinListing27-39isaskingthecontentprovidertoruntheoperationindicatedbylistindexindexOfTheOperationWhoseKeyYouNeedandtakeitsgeneratedprimarykeyanduseitasavalueforthecolumnthatissetonthetargetoperation.Thisishowyoutaketheinsertfromrawcontactanduseitsprimarykeyasthekeyvalueforthedataitemsbelongingtothatrawcontact.

OptimisticLockingInoptimisticlocking,youfirstapplythetransactionswithoutlockingtheunderlyingrepositoryandseeifanyupdateshavebeenmadesinceyouknowitsvaluebefore.Ifso,cancelthetransactionandretryit.

Tomakethisinthebatchmode,theAPIoffersatypeofoperationcalledanassertquery.Inthistypeofoperationthecontentprovidermakesthequeryandcomparesthevaluesoftheretrievedcursorforeitherthecountorthevaluesofcertainkeys.Iftheydon’tmatch,itrollsbackthetransactionandraisesanexceptionbreakingthecodeflow.SeethisdemonstratedinthecodeshowninListing27-40.

Listing27-40.UsingOptimisticLockingthroughnewAssertQuery

try{//ReadarawcontactforaparticularrawcontactidContentProviderOperation.BuilderassertOpBuilder=ContentProviderOperation.newAssertQuery(rawContactUri);//MakesurethereisonlyonerawcontactwiththatdetailsassertOpBuilder.withExpectedCount(1);//Makesuretheversioncolumnmatcheswithyoustartedwith//Ifnotthrowanexception.Wechosetocomparetheversionnumber//column(field)intherawcontactstabletoassert.assertOpBuilder.withValue(SyncColumns.VERSION,mVersion);//getthisoperationandaddittotheoperationslistattheend//Applythebatch…activityInstance.getContentResolver().applyBatch(...);}//forthisorotherexceptionscatch(OperationApplicationExceptione){//Thebatchisalreadycancelled//Telltheusertheupdatefailed//Showtheuserthenewdetailsandrepeattheprocess}

ReusingtheContactProviderUIContactprovidercapabilityinAndroidalsodefinesasetofintentsthatcanbeusedtoreusetheUIavailableinthecontactsapplication.

Therearethreekindsofintents.ThereisasetofintentsthatthecontactproviderfiresbasedontheeventstakingplaceinthecontentproviderUIapplication.Forexample,theintentINVITE_CONTACTisfiredwhentheuserclicksthe“invitetothenetwork”buttononacontactinthecontactapplication.Anapplicationcanregisterforthiseventandreadthecontactdetails.

Thereisanothersetofintentsthatareusedwhenthecontactprovideractsasasearchproviderforyourcustomactivities.Usingthisfacilityyoucansearchforacontactinyourcustomapplicationthroughsearchsuggestions.

ThereisanothersetofintentsthatexternalapplicationscanfiretoreusetheUIthatisprovidedbythecontactapplication.Youcanusetheseintentstopickfromalistofcontacts,orfromalistofphonenumbers,orfromalistofaddresses,orfromalistofe-mails.YoucanalsousetheseintentstoupdateacontactorcreateacontactusingtheUIprovidedtheAndroidapplication.

TheseintentsaredocumentedintheclassreferenceforContactsContract.Intents.

UsingGroupFeaturesContactsAPIprovidesthecontractsshowninListing27-41toworkwiththeGroupFeaturesofcontacts

Listing27-41.GroupContactContracts

ContactsContract.GroupsContactsContract.CommonDataKinds.GroupMembership

Thegroupstableholdsthingslikenameofthegroup,notesaboutthatgroup,andsomegrouplevelcountsofthemembership.Thegroupsarawcontactbelongstoarekeptinthedatatables.

UsingPhotoFeaturesYoucanexplorethephoto-relatedinformationforacontactusingtheclasscontractshowninListing27-42.

Listing27-42.ContactPhotoContracts

ContactsContract.Contacts.PhotoContactsContract.RawContacts.DisplayPhoto

Theclassdocumentationforthesecontractshassamplecodethatdescribeshowtousethesefeatures.

ReferencesHereareadditionalresourcesforthetopicscoveredinthischapter:

https://developer.android.com/guide/topics/providers/contacts-provider.html:TheprimarydocumentationforallaspectsoftheContactsAPIfromGoogle.ThisURLalsoincludesasectiononperformingbatchoperationsonthecontactsdatabase,optimisticlocking,andreusingthecontactsapplicationUI.

http://developer.android.com/reference/android/provider/ContactsContract.htmlJavadocforthekeyJavaclassContactsContract.YouwillneedthisURLoftenasyoucodetotheContactsAPI.

https://play.google.com/store/books/details/Google_Android_Quick_Start_Guide_Android_5_0_Lolli?id=dnzVBAAAQBAJ:Android5.0QuickStartguide.TheseAndroiduserguidesthatarepreparedforeachreleaseareusefulin

understandinghowthestockcontactsapplicationworkfromaUIperspective.

https://developer.android.com/guide/topics/providers/contacts-provider.html#SyncAdapters:SyncAdaptersaredocumentedhere.

http://developer.android.com/sdk/android-4.0.html#Contacts:DocumentationforthechangestotheContactsAPIin4.0.

http://developer.android.com/reference/android/provider/ContactsContract.Profile.htmlAreferenceonhowtousethenewProfileURIsintroducedin4.0.

http://www.androidbook.com/item/3917:EntrypointforourresearchontheContactsAPI.Youwillfindhereourresearch,asummaryoftheContactsAPI,tablesusedinthecontactsdatabase,howtoexplorethecontactsdatabases,contactsapplicationscreenshots,howtoexploresourcesforcontactproviders,andotherusefullinks.

http://developer.android.com/guide/topics/search/index.htmlSDKdocsonSearchAPI.Usefultoreviewthistoknowhowtosearchforcontacts.

http://www.androidbook.com/proandroid5/projects:YoucanusethisURLtodownloadthetestprojectdedicatedforthischapter.ThenameoftheZIPfileisProAndroid5_ch27_TestContacts.zip.

SummaryInthischapter,wehavecoveredthefollowing:thenatureoftheContactsAPI,exploringthecontactsdatabase,exploringtheContactsAPIURIsandtheircursors,readingandaddingcontacts,aggregatingrawcontacts,therelationshipbetweenthepersonalprofileandcontacts,andreadingandaddingcontactstoapersonalprofile.Wehavealsobrieflycoveredbatchingprovideroperations,usingthecontactproviderasasearchproviderforcontacts.

Chapter28

ExploringSecurityandPermissionsNoexplorationofmoderndevelopmentplatformsoroperatingsystemsiscompletewithoutdiscussingsecurity.InAndroid,securityspansallphasesoftheapplicationlifecycle—fromdesign-timepolicyconsiderationstoruntimeboundarychecks.Inthischapteryou’lllearnAndroid’ssecurityarchitectureandunderstandhowtodesignsecureapplications.

Let’sgetstartedwiththeAndroidsecuritymodel.

UnderstandingtheAndroidSecurityModelLet’sdiverightin,tocoversecurityduringthedeploymentandexecutionofanyAndroidapplication.TodeployanAndroidapplication,itmustbesignedwithadigitalcertificateinorderforyoutoinstallitontoadevice.Withrespecttoexecution,Androidrunseachapplicationwithinaseparateprocess,whereeachprocesshasauniqueandpermanentuserID(assignedatinstalltime).Thisplacesaboundaryaroundtheprocessandpreventsoneapplicationfromhavingdirectaccesstoanother’sdata.Moreover,Androiddefinesadeclarativepermissionmodelthatprotectssensitivefeatures(suchasthecontactlist).

Inthenextseveralsections,wearegoingtodiscussthesetopics.Butbeforewegetstarted,let’sprovideanoverviewofsomeofthesecurityconceptsthatwe’llrefertolater.

OverviewofSecurityConceptsAndroidrequiresthatapplicationsbesignedwithadigitalcertificate.Oneofthebenefitsofthisrequirementisthatanapplicationcannotbeupdatedwithaversionthatwasnotpublishedbytheoriginalauthororholderofthesigningcertificate.Ifwepublishanapplication,forexample,thenyoucannotupdateourapplicationwithyourversion(unless,ofcourse,yousomehowobtainourcertificate).Thatsaid,whatdoesitmeanforanapplicationtobesigned?Andwhatistheprocessofsigninganapplication?

Yousignanapplicationwithadigitalcertificate.Adigitalcertificateisanartifactthatcontainsinformationaboutyou,suchasyourcompanyname,address,andsoon.Afewimportantattributesofadigitalcertificateincludeitssignatureandpublic/privatekey.Apublic/privatekeyisalsocalledakeypair.Notethatalthoughyouusedigitalcertificatesheretosign.apkfiles,youcanalsousethemforotherpurposes(suchasencryptedcommunication,signingdocuments,andsoforth).Youcanobtainadigitalcertificatefromatrustedcertificateauthority(CA)andyoucanalsogenerateoneyourselfusingtoolssuchasthekeytool,whichwe’lldiscussshortly.Digitalcertificatesarestoredinkeystores.Akeystorecontainsalistofdigitalcertificates,eachofwhichhasanaliasthatyoucanuse

torefertoitinthekeystore.

SigninganAndroidapplicationrequiresthreethings:adigitalcertificate,the.apkfilefortheapplicationyouwishtosign,andautilitythatknowshowtoapplyadigitalsignaturetothe.apkfile.WeuseafreeutilitythatispartoftheJavaDevelopmentKit(JDK)distributioncalledthejarsigner.Thisutilityisacommand-linetoolthatknowshowtosigna.jarfileusingadigitalcertificate,andan.apkfileisreallyjustazip-formattedfilethatcollectstogether.jarfilesandafewotherresourcesforyourproject.Othersigningtoolsareavailable,soyouarefreetochoosethetoolthatworksbestforyou.

Now,let’smoveonandtalkabouthowyoucansignan.apkfilewithadigitalcertificate.

SigningApplicationsforDeploymentToinstallanAndroidapplicationontoadevice,youfirstneedtosigntheAndroidpackage(.apkfile)usingadigitalcertificate.Thecertificate,however,canbeself-signed—youdonotneedtopurchaseacertificatefromacertificateauthoritysuchasVeriSign.Beawarethatself-signedcertificatesaregenerallyconsideredlesstrustworthy,andinsomeenvironmentsareconsideredinsecure.

Signingyourapplicationfordeploymentinvolvesthreesteps.Thefirststepistogenerateacertificateusingkeytool(orasimilartool).Thesecondstepinvolvesusingthejarsignertooltosignthe.apkfilewiththegeneratedcertificate.Thethirdstepalignsportionsofyourapplicationonmemoryboundariesformoreefficientmemoryusagewhenrunningonadevice.Notethatduringdevelopment,boththeADTplug-inforEclipseandAndroidDeveloperStudiotakecareofeverythingforyou:signingyour.apkfileanddoingthememoryalignment,beforedeployingontotheemulatororadevice.

GeneratingaSelf-SignedCertificateUsingtheKeytoolThekeytoolutilitymanagesadatabaseofprivatekeysandtheircorrespondingX.509certificates(astandardfordigitalcertificates).ThisutilityshipswiththeJDKandresidesundertheJDKbindirectory.IfyoufollowedtheinstructionsinChapter2regardingchangingyourPATH,theJDKbindirectoryshouldalreadybeinyourPATH.Inthissection,we’llshowyouhowtogenerateakeystorewithasingleentry,whichyou’lllaterusetosignanAndroid.apkfile.Togenerateakeystoreentry,dothefollowing:

1. Createafoldertoholdthekeystore,suchasc:\android\release\.or/opt/android/release(dependingonyouroperatingsystem).

2. Openashellorcommandwindow,andexecutethekeytoolutilitywiththeparametersshowninListing28-1.

Listing28-1.GeneratingaKeystoreEntryUsingthekeytoolUtility

keytool-genkey-v-keystore"c:\android\release\release.keystore"-aliasandroidbook-keyalgRSA-validity14000

AlloftheargumentspassedtothekeytoolaresummarizedinTable28-1.

Table28-1.ArgumentsPassedtothekeytoolUtility

Argument Description

genkey Tellskeytooltogenerateapublic/privatekeypair.

v Tellskeytooltoemitverboseoutputduringkeygeneration.

keystore Pathtothekeystoredatabase(inthiscase,afile).Thefilewillbecreatedifnecessary.

alias Uniquenameforthekeystoreentry.Thisaliasisusedlatertorefertothekeystoreentry.

keyalg Algorithm.

validity Validityperiod.

keytoolwillpromptyoufortwopasswordsduringthecreationofthekeystoreandtheentryyouarecreating.Thefirstpasswordpromptedisforthekeystoreitselfandcontrolsaccesstoallthekeymaterialyouwillstore.Thiscanalsobespecifiedusingthestorepassparameter.Thesecondpasswordisthepasswordfortheprivatekeyandrelatedcertificateyouarecreating,alsoavailableviathekeypassparameter.Youshouldgetusedtonotincludingtheseasparametersonthecommandline,andinsteadprefertoallowkeytooltopromptyouasgoodgeneralsecuritypractice.

Beaware,thatifyoudousetheparametersforpasswordtokeytool,anyonewhogetsaccesstoyourshellorcommand-linehistorycanseethepasswords,ascananyonewhocanlisttherunningprocessesonyourmachinewhilekeytoolruns.ThecommandinListing28-1willgenerateakeystoredatabasefileinyourkeystorefolder.Thedatabasewillbeafilenamedrelease.keystore.Thevalidityoftheentrywillbe14,000days(orapproximately38years)—whichisalongtimefromnow.Youshouldunderstandthereasonforthis.TheAndroiddocumentationrecommendsthatyouspecifyavalidityperiodlongenoughtosurpasstheentirelifespanoftheapplication,whichwillincludemanyupdatestotheapplication.Itrecommendsthatthevaliditybeatleast25years.IfyouplantopublishtheapplicationonGooglePlay,yourcertificatewillneedtobevalidthroughatleastOctober22,2033.GooglePlaycheckseachapplicationwhenuploadedtomakesureitwillbevalidatleastuntilthen.

CautionBecauseyourcertificateinanyapplicationupdatemustmatchthecertificateyouusedthefirsttime,makesureyousafeguardyourkeymaterial.Keepeitheryourkeystorefile,orthekeypairifyouchoosetoexportthem,safe!Ifyouloseaccesstothekeystoreorunderlyingkeys,andyoucan’tre-createit,youwon’tbeabletoupdateyourapplication,andyou’llhavetoissueawholenewapplicationinstead.

Goingbacktothekeytool,theargumentaliasisauniquenamegiventotheentryinthekeystoredatabase;youwillusethisnamelatertorefertotheentry.WhenyourunthekeytoolcommandinListing28-1,keytoolwillaskyouafewquestions(seeFigure28-1)andthengeneratethekeystoredatabaseandentry.

Figure28-1.Additionalquestionsaskedbykeytool

Onceyouhaveakeystorefileforyourproductioncertificates,youcanreusethisfiletoaddmorecertificates.Justusekeytoolagain,andspecifyyourexistingkeystorefile.

TheDebugKeystoreandtheDevelopmentCertificateWementionedthattheADTplug-inforEclipse,andAndroidDeveloperStudio,bothtakecareofsettingupadevelopmentkeystoreforyou.However,thedefaultcertificateusedforsigningduringdevelopmentcannotbeusedforproductiondeploymentontoarealdevice.Thisispartlybecausetheautomaticallygenerateddevelopmentcertificateisonlyvalidfor365days,whichclearlydoesnotgetyoupastOctober22,2033.Sowhathappensonthethreehundredsixty-sixthdayofdevelopment?You’llgetabuilderror.Yourexistingapplicationsshouldstillrun,buttobuildanewversionofanapplication,youneedtogenerateanewcertificate.Theeasiestwaytodothisistodeletetheexistingdebug.keystorefile,andassoonasitisneededagain,theADT(forinstance)willgenerateanewfileandcertificatevalidforanother365days.

Tofindyourdebug.keystorefile,assumingyouareusingEclipsewithADT,openthePreferencesscreenofEclipseandgotoAndroid Build.Thedebugcertificate’slocationwillbedisplayedintheDefaultDebugKeystorefield,asshowninFigure28-2(seeChapter2ifyouhavetroublefindingthePreferencesmenu).

Figure28-2.Thedebugcertificate’slocation

Ofcourse,nowthatyou’vegotanewdevelopmentcertificate,youcannotupdateyourexistingapplicationsinAndroidVirtualDevices(AVDs)orondevicesusinganewdevelopmentcertificate.EclipsewillprovidemessagesintheConsoletellingyoutouninstalltheexistingapplicationfirstusingadb,whichyoucancertainlydo.IfyouhavealotofyourapplicationsinstalledontoanAVD,youmayfeelitiseasiertosimplyre-createtheAVD,soitdoesnotcontainanyofyourapplicationsandyoucanstartfresh.Toavoidthisproblemayearfromnow,youcouldgenerateyourowndebug.keystorefilewithwhatevervalidityperiodyoudesire.Obviously,itneedstohavethesamefilenameandbeinthesamedirectoryasthefilethatADTwouldcreate.Thecertificatealiasisandroiddebugkey,andthestorepassandkeypassareboth“android”.ADTsetsthefirstandlastnameonthecertificateas“AndroidDebug”,theorganizationalunitas“Android”,andthetwo-lettercountrycodeas“US”.Youcanleavetheorganization,city,andstatevaluesas“Unknown”.

Ifyouacquiredamap-apikeyfromGoogleusingtheolddebugcertificate,youwillneedtogetanewmap-apikeytomatchthenewdebugcertificate.Wecoveredmap-apikeysinChapter19.

Nowthatyouhaveadigitalcertificatethatyoucanusetosignyourproduction.apkfile,youneedtousethejarsignertooltodothesigning.Here’showtodothat.

UsingtheJarsignerTooltoSignthe.apkFileThekeytoolutilitydescribedintheprevioussectioncreatedadigitalcertificate,whichisoneoftheparametersforthejarsignertool.TheotherparameterforjarsigneristheactualAndroidpackagetobesigned.TogenerateanAndroidpackage,youneedtousetheExportUnsignedApplicationPackageutilityintheADTplug-inforEclipse(orequivalentfunctioninAndroidDeveloperStudio).Youaccesstheutilitybyright-clicking

anAndroidprojectinEclipse,selectingAndroidTools,andselectingExportUnsignedApplicationPackage.RunningtheExportUnsignedApplicationPackageutilitywillgeneratean.apkfilethatwillnotbesignedwiththedebugcertificate.

Toseehowthisworks,runtheExportUnsignedApplicationPackageutilityononeofyourAndroidprojects,andstorethegenerated.apkfilesomewhere.Forthisexample,we’llusethekeystorefolderwecreatedearlierandgeneratean.apkfilecalledc:\android\release\myappraw.apk.

Withthe.apkfileandthekeystoreentry,runthejarsignertooltosignthe.apkfile(seeListing28-2).Usethefullpathnamestoyourkeystorefileand.apkfileasappropriatewhenyourunthis.

Listing28-2.UsingjarsignertoSignthe.apkFile

jarsigner-keystore"PATHTOYOURrelease.keystoreFILE"-storepasspaxxword-keypasspaxxword"PATHTOYOURRAWAPKFILE"androidbook

Tosignthe.apkfile,youpassthelocationofthekeystore,thekeystorepassword,theprivate-keypassword,thepathtothe.apkfile,andthealiasforthekeystoreentry.Thejarsignerwillthensignthe.apkfilewiththedigitalcertificatefromthekeystoreentry.Torunthejarsignertool,youwillneedtoeitheropenatoolswindow(asexplainedinChapter2)oropenacommandorTerminalwindowandeithernavigatetotheJDKbindirectoryorensurethatyourJDKbindirectoryisonthesystempath.Forsecurityreasons,itissafertoleaveoffthepasswordargumentstothecommandandsimplyletjarsignerpromptyouasnecessaryforpasswords.Figure28-3showswhatthejarsignertoolinvocationlookslike.YoumayhavenoticedthatjarsignerpromptedforonlyonepasswordinFigure28-3.Jarsignerfiguresoutnottoaskforthekeypasspasswordwhenthestorepassandkeypassarethesame.Strictlyspeaking,thejarsignercommandinListing28-2onlyneeds–keypassifithasadifferentpasswordthan–storepass.

Figure28-3.Usingjarsigner

Aswepointedoutearlier,Androidrequiresthatanapplicationbesignedwithadigitalsignaturetopreventamaliciousprogrammerfromupdatingyourapplicationwiththeirversion.Forthistowork,Androidrequiresthatupdatestoanapplicationbesignedwiththesamesignatureastheoriginal.Ifyousigntheapplicationwithadifferentsignature,Androidtreatsthemastwodifferentapplications.Soweremindyouagain,becarefulwithyourkeystorefilesoit’savailabletoyoulaterwhenyouneedtoprovideanupdatetoyour

application.

AligningYourApplicationwithzipalignYouwantyourapplicationtobeasmemoryefficientaspossiblewhenrunningonadevice.Ifyourapplicationcontainsuncompresseddata(perhapscertainimagetypesordatafiles)atruntime,Androidcanmapthisdatastraightintomemoryusingthemmap()call.Forthistowork,though,thedatamustbealignedona4-bytememoryboundary.TheCPUsinAndroiddevicesare32-bitprocessors,and32bitsequals4bytes.Themmap()callmakesthedatainyour.apkfilelooklikememory,butifthedataisnotalignedona4-byteboundary,itcan’tdothatandextracopyingofdatamustoccuratruntime.Thezipaligntool,foundintheAndroidSDKbuildorbuild-tools/<version>directory,looksthroughyourapplicationandmovesslightlyanyuncompresseddatanotalreadyona4-bytememoryboundarytoa4-bytememoryboundary.Youmayseethefilesizeofyourapplicationincreaseslightlybutnotsignificantly.Toperformanalignmentonyour.apkfile,usethiscommandinatoolswindow(seealsoFigure28-4):

zipalign–v4infile.apkoutfile.apk

Figure28-4.Usingzipalign

Notethatzipaligndoesnotmodifytheinputfile,sothisiswhywechosetouse“raw”aspartofourfilenamewhenexportingfromEclipse.Now,ouroutputfilehasanappropriatenamefordeployment.Ifyouneedtooverwriteanexistingoutfile.apkfile,youcanusethe–foption.Alsonotethatzipalignperformsaverificationofthealignmentwhenyoucreateyouralignedfile.Toverifythatanexistingfileisproperlyaligned,usezipaligninthefollowingway:

zipalign–c–v4filename.apk

Itisveryimportantthatyoualignaftersigning;otherwise,signingcouldcausethingstogobackoutofalignment.Thisdoesnotmeanyourapplicationwouldcrash,butitcouldusemorememorythanitneedsto.

UsingtheExportWizardInEclipse,youmayhavenoticedamenuchoiceunderAndroidToolscalledExport

SignedApplicationPackage.Thislauncheswhatiscalledtheexportwizard,anditdoesallofthepreviousstepsforyou,promptingonlyforthepathtoyourkeystorefile,keyalias,thepasswords,andthenameofyouroutput.apkfile.Itwillevencreateanewkeystoreornewkeyifyouneedone.Youmayfinditeasiertousethewizard,oryoumayprefertoscriptthestepsyourselftooperateonanexportedunsignedapplicationpackage.Nowthatyouknowhoweachworks,youcandecidewhichisbetterforyou.

ManuallyInstallingAppsOnceyouhavesignedandalignedan.apkfile,youcaninstallitontothevirtualdevicemanuallyusingtheadbtool.Asanexercise,startthevirtualdevicefromtheAVDManager,whichwillstartwithoutcopyingoveranyofyourdevelopmentprojectsfromEclipse.Now,openatoolswindowandruntheadbtoolwiththeinstallcommand:

adbinstall"PATHTOAPKFILEGOESHERE"

Thismayfailforacoupleofreasons,butthemostlikelyarethatthedebugversionofyourapplicationwasalreadyinstalledontheemulator,givingyouacertificateerror,orthereleaseversionofyourapplicationwasalreadyinstalledontheemulator,givingyouan“INSTALL_FAILED_ALREADY_EXISTS”error.Inthefirstcase,youcanuninstallthedebugapplicationwiththiscommand:

adbuninstallpackagename

Notethattheargumenttouninstallistheapplication’spackagenameandnotthe.apkfilename.ThepackagenameisdefinedintheAndroidManifest.xmlfileoftheinstalledapplication.

Forthesecondcase,youcanusethiscommand,where–rsaystoreinstalltheapplicationwhilekeepingitsdataonthedevice(oremulator):

adbinstall–r"PATHTOAPKFILEGOESHERE"

Now,let’sseehowsigningaffectstheprocessofupdatinganapplication.

InstallingUpdatestoanApplicationandSigningEarlier,wementionedthatacertificatehasanexpirationdateandthatGooglerecommendsyousetexpirationdatesfarintothefuture,toaccountforalotofapplicationupdates.Thatsaid,whathappensifthecertificatedoesexpire?WouldAndroidstillruntheapplication?Fortunately,yes—Androidteststhecertificate’sexpirationonlyatinstalltime.Onceyourapplicationisinstalled,itwillcontinuetorunevenifthecertificateexpires.

Butwhataboutupdates?Unfortunately,youwillnotbeabletoupdatetheapplicationoncethecertificateexpires.Inotherwords,asGooglesuggests,youneedtomakesurethelifeofthecertificateislongenoughtosupporttheentirelifeoftheapplication.Ifacertificatedoesexpire,Androidwillnotinstallanupdatetotheapplication.Theonlychoiceleftwillbeforyoutocreateanotherapplication—anapplicationwithadifferentpackagename—

andsignitwithanewcertificate.Soasyoucansee,itiscriticalforyoutoconsidertheexpirationdateofthecertificatewhenyougenerateit.

Nowthatyouunderstandsecuritywithrespecttodeploymentandinstallation,let’smoveontoruntimesecurityinAndroid.

PerformingRuntimeSecurityChecksRuntimesecurityinAndroidhappensattheprocessandoperationlevels.Attheprocesslevel,Androidpreventsoneapplicationfromdirectlyaccessinganotherapplication’sdata.ItdoesthisbyrunningeachapplicationwithinadifferentprocessandunderauniqueandpermanentuserID.Attheoperationallevel,Androiddefinesalistofprotectedfeaturesandresources.Foryourapplicationtoaccessthisinformation,youhavetoaddoneormorepermissionrequeststoyourAndroidManifest.xmlfile.Youcanalsodefinecustompermissionswithyourapplication.

Inthesectionsthatfollow,wewilltalkaboutprocess-boundarysecurityandhowtodeclareandusepredefinedpermissions.Wewillalsodiscusscreatingcustompermissionsandenforcingthemwithinyourapplication.Let’sstartbydissectingAndroidsecurityattheprocessboundary.

UnderstandingSecurityattheProcessBoundaryUnlikeyourdesktopenvironment,wheremostoftheapplicationsrununderthesameuserID,eachAndroidapplicationgenerallyrunsunderitsownuniqueID.ByrunningeachapplicationunderadifferentID,Androidcreatesanisolationboundaryaroundeachprocess.Thispreventsoneapplicationfromdirectlyaccessinganotherapplication’sdata.

Althougheachprocesshasaboundaryaroundit,datasharingbetweenapplicationsisobviouslypossiblebuthastobeexplicit.Inotherwords,togetdatafromanotherapplication,youhavetogothroughthecomponentsofthatapplication.Forexample,youcanqueryacontentproviderofanotherapplication,youcaninvokeanactivityinanotherapplication,or—asyou’llseeinChapter15—youcancommunicatewithaserviceofanotherapplication.Allofthesefacilitiesprovidemethodsforyoutoshareinformationbetweenapplications,buttheydosoinanexplicitmannerbecauseyoudon’tdirectlyaccesstheunderlyingdatabase,files,andsoon.

Android’ssecurityattheprocessboundaryisclearandsimple.Thingsgetinterestingwhenwestarttalkingaboutprotectingresources(suchascontactdata),features(suchasthedevice’scamera),andourowncomponents.Toprovidethisprotection,Androiddefinesapermissionscheme.Let’sdissectthatnow.

DeclaringandUsingPermissionsAndroiddefinesapermissionschememeanttoprotectresourcesandfeaturesonthedevice.Forexample,applications,bydefault,cannotaccessthecontactslist,makephonecalls,andsoon.Toprotecttheuserfrommaliciousapplications,Androidrequires

applicationstorequestpermissionsiftheyneedtouseaprotectedfeatureorresource.FromtheintroductionofAndroidKitKat,andcontinuinginAndroidLollipop,permissionswhenpresentedtotheenduserarenowclusteredintogroupstoaddresstheirconstantlygrowingnumberandcomplexity.Thisgroupingbringswithitsomecompromisesasyouwillobserve.

Aswewillcovershortly,permissionrequestsgointhemanifestfile.Atinstalltime,theAPKinstallereithergrantsordeniestherequestedpermissionsbasedonthesignatureofthe.apkfileand/orfeedbackfromtheuser.Ifpermissionisnotgranted,anyattempttoexecuteoraccesstheassociatedfeaturewillresultinapermissionfailure.

Table28-2showssomecommonlyusedfeaturesandthepermissionstheyrequire.Althoughyouarenotyetfamiliarwithallthefeatureslisted,youwilllearnaboutthemlater(eitherinthischapterorinsubsequentchapters).

Table28-2.FeaturesandResourcesandthePermissionsTheyRequire

Feature/ResourceRequiredPermission Description

Camera android.permission.CAMERA Enablesyoutoaccessthedevice’scamera.

Internet android.permission.INTERNET Enablesyoutomakeanetworkconnection.

User’scontactdata

android.permission.READ_CONTACTS

android.permission.WRITE_CONTACTSEnablesyoutoreadfromorwritetotheuser’scontactdata.

User’scalendardata

android.permission.READ_CALENDAR

android.permission.WRITE_CALENDAREnablesyoutoreadfromorwritetotheuser’scalendardata.

Recordingaudio

android.permission.RECORD_AUDIO Enablesyoutorecordaudio.

Wi-Filocationinformation

android.permission.ACCESS_COARSE_LOCATIONEnablesyoutoaccesscoarse-grainedlocationinformationfromWi-Fiandcelltowers.

GPSlocationinformation

android.permission.ACCESS_FINE_LOCATION

Enablesyoutoaccessfine-grainedlocationinformation.ThisincludesGPSlocationinformation.ItisalsosufficientforWi-Fiandcelltowers.

Batteryinformation

android.permission.BATTERY_STATS Enablesyoutoobtainbattery-stateinformation.

Bluetooth android.permission.BLUETOOTH EnablesyoutoconnecttopairedBluetoothdevices.

Foracompletelistofpermissions,seethefollowingURL:

http://developer.android.com/reference/android/Manifest.permission.html

Applicationdeveloperscanrequestpermissionsbyaddingentriestothe

AndroidManifest.xmlfile.Forexample,Listing28-3askstoaccessthecameraonthedevice,toreadthelistofcontacts,andtoreadthecalendar.

Listing28-3.PermissionsinAndroidManifest.xml

<manifest…><application>...</application><uses-permissionandroid:name="android.permission.CAMERA"/><uses-permissionandroid:name="android.permission.READ_CONTACTS"/><uses-permissionandroid:name="android.permission.READ_CALENDAR"/></manifest>

Notethatyoucaneitherhard-codepermissionsintheAndroidManifest.xmlfileorusethemanifesteditor.Themanifesteditoriswireduptolaunchwhenyouopen(double-click)themanifestfile.Themanifesteditorcontainsadrop-downlistthathasallofthepermissionspreloadedtopreventyoufrommakingamistake.AsshowninFigure28-5,youcanaccessthepermissionslistbyselectingthePermissionstabinthemanifesteditor.

Figure28-5.TheAndroidmanifesteditortoolinEclipse

YounowknowthatAndroiddefinesasetofpermissionsthatprotectsasetoffeaturesandresources.Similarly,youcandefineandenforcecustompermissionswithyourapplication.Let’sseehowthatworks.

UnderstandingandUsingURIPermissions

Contentproviders(discussedinChapter4)oftenneedtocontrolaccessatafinerlevelthanallornothing.Fortunately,Androidprovidesamechanismforthis.Thinkaboute-mailattachments.Theattachmentmayneedtobereadbyanotheractivitytodisplayit.Buttheotheractivityshouldnotgetaccesstoallofthee-maildataanddoesnotneedaccesseventoallattachments.ThisiswhereURIpermissionscomein.

PassingURIPermissionsinIntentsWheninvokinganotheractivityandpassingaURI,yourapplicationcanspecifythatitisgrantingpermissionstotheURIbeingpassed.Butbeforeyourapplicationcandothis,itneedspermissionitselftotheURI,andtheURIcontentprovidermustcooperateandallowthegrantingofpermissionstoanotheractivity.ThecodetoinvokeanactivitywithgrantingofpermissionslookslikeListing28-4,whichisactuallyfromtheAndroidEmailprogram,whereitislaunchinganactivitytoviewane-mailattachment.

Listing28-4.CodetoLaunchanActivitywithGrantingofPermission

try{Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(contentUri);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);startActivity(intent);}catch(ActivityNotFoundExceptione){mHandler.attachmentViewError();//TODO:Addaproperwarningmessage(andlotsofupstreamcleanuptoprevent//itfromhappening)inthenextrelease.}

TheattachmentisspecifiedbycontentUri.NoticehowtheintentiscreatedwiththeactionIntent.ACTION_VIEW,andthedataissetusingsetData().Theflagissettograntreadpermissionoftheattachmenttowhateveractivitywillmatchontheintent.Thisiswherethecontentprovidercomesintoplay.Justbecauseanactivityhasreadpermissiontocontentdoesn’tmeanitcanpassalongthatpermissiontosomeotheractivitythatdoesnothavethepermissionalready.Thecontentprovidermustallowitaswell.AsAndroidfindsamatchingintentfilteronanactivity,itconsultswiththecontentprovidertomakesurethatpermissionscanbegranted.Inessence,thecontentproviderisbeingaskedtoallowaccesstothisnewactivitytothecontentspecifiedbytheURI.Ifthecontentproviderrefuses,thenaSecurityExceptionisthrown,andtheoperationfails.InListing28-4,thisparticularapplicationisnotcheckingforaSecurityException,becausethedeveloperisnotexpectinganyrefusalstograntpermission.That’sbecausetheattachmentcontentproviderispartoftheEmailapplication!Thereisapossibilitythoughthatnoactivitycanbefoundtohandletheattachment,sothatistheonlyexceptionbeingwatchedfor.

InthecasewheretheactivitybeingcalledtoprocesstheURIalreadyhaspermissiontoaccessthatURI,thecontentproviderdoesnotgettodenyaccess.Thatis,thecallingactivitycangrantpermission,andiftheactivityonthereceivingendoftheintentalready

hasthenecessarypermissionsforcontentURI,thecalledactivitywillbeallowedtoproceedwithnoproblems.

InadditiontoIntent.FLAG_GRANT_READ_URI_PERMISSION,thereisaflagforwritepermissions:Intent.FLAG_GRANT_WRITE_URI_PERMISSION.ItispossibletospecifybothinanIntent.Also,theseflagscanapplytoservicesandBroadcastReceiversaswellasactivitiesbecausetheycanreceiveintentstoo.

SpecifyingURIPermissionsinContentProvidersSohowdoesacontentproviderspecifyURIpermissions?ItdoessointheAndroidManifest.xmlfileinoneoftwoways:

Inthe<provider>tag,theandroid:grantUriPermissionsattributecanbesettoeithertrueorfalse.Iftrue,anycontentfromthiscontentprovidercanbegranted.Iffalse,thesecondwayofspecifyingURIpermissionscanhappen,orthecontentprovidercandecidenottoletanyoneelsegrantpermissions.

Specifypermissionswithchildtagsof<provider>.Thechildtagis<grant-uri-permission>,andyoucanhavemorethanonewithin<provider>.<grant-uri-permission>hasthreepossibleattributes:

Usingtheandroid:pathattribute,youcanspecifyacompletepathwhichwillthenhavepermissionsthataregrantable.

Similarly,android:pathPrefixspecifiesthebeginningofaURIpath.

android:pathPatternallowswildcards(theasterisk,*,character)tospecifyapath.

Aswestatedbefore,thegrantingentitymustalsohaveappropriatepermissionstothecontentbeforebeingallowedtograntthemtosomeotherentity.Contentprovidershaveadditionalwaysofcontrollingaccesstotheircontent,throughtheandroid:readPermissionattributeofthe<provider>tag,theandroid:writePermissionattribute,andtheandroid:permissionattribute(aconvenientwaytospecifybothreadandwritepermissionswithonepermissionStringvalue).ThevalueforanyofthesethreeattributesisaStringthatrepresentsthepermissionacallermusthaveinordertoreadorwritewiththiscontentprovider.BeforeanactivitycouldgrantreadpermissiontoacontentURI,thatactivitymusthavereadpermissionfirst,asspecifiedbyeithertheandroid:readPermissionattributeortheandroid:permissionattribute.Theentitywantingthesepermissionswoulddeclarethemintheirmanifestfilewiththe<uses-permissions>tag.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://developer.android.com/guide/topics/security/security.htmlTheAndroidDeveloper’sGuidesection“SecurityTips.”Itprovidesanoverviewwithlinkstolotsofreferencepages.

http://developer.android.com/guide/publishing/app-signing.html:TheAndroidDeveloper’sGuidesection“SigningYourApplications.”

SummaryThissecuritychaptercoveredthefollowingtopics:

UniqueapplicationuserIDsthathelpseparateappsfromeachothertoprotectprocessinganddata

DigitalcertificatesandtheiruseinsigningAndroidapplications

Thatanapplicationcanonlybeupdatediftheupdateissignedwiththesamedigitalcertificateastheoriginal

Managingcertificatesinakeystoreusingkeytool

Runningjarsignertoapplyacertificatetoanapplication.apkfile

zipalignandmemoryboundaries

TheEclipseplug-inwizardtakescareofgeneratingtheapk,applyingthecertificateandzipalign-ingforyou

Manuallyinstallingappsontodevicesandemulators

Permissionsthatapplicationscandeclareanduse

URIpermissionsandhowcontentprovidersusethem

Chapter29

UsingGoogleCloudMessagingwithAndroidAsweapproachtheendofthebook,youwillhavealreadydevelopedagoodunderstandingof,andappreciationfor,themanycommunicationprotocolsandarchitecturaloptionsyouhaveavailablewithinAndroidwhenitcomestodealingwithoff-deviceservices.Inthischapter,wewillexploreGoogle’sCloudMessaging(orGCM)platformandhowyoucanuseitastheplumbingforyourapplication’sremotecommunicationandserviceinteractionneeds.

WhatIsGoogleCloudMessaging?GCMisaserviceofferedbyGoogletoenableyoutowritemultipleapplicationsacrossdifferentplatformsthatexchangemessagesinordertofurthertheirfunctionality.TheprimaryexamplesofthemultipleapplicationsareanAndroidclientapplicationexchangingmessageswitharemoteserverapplication.

Theactualmessagessent,andtheirpurpose,areuptoyouasadeveloper.Itcouldbeamessagefromtheremoteserverlettingyourclientapplicationknow(a“downstream”message)thatanewupdatetoanewsfeed,musicservice,orsimilarsubscriptionisavailable.Messagesfromtheclienttotheserver(an“upstream”message)couldbesendingachatmessage,picturethumbnail,orothernewpieceofdatayouruserhascapturedorgeneratedontheclient.Thesearejustexamples—youarefreetoimagineanyuse,andanypayload,forthemessagesexchangedinGCM.

UnderstandingtheKeyBuildingBlocksofGCMHavingreadtheintroductiontoGCM,youarealreadyawareoftwoofthekeycomponentsinanycompleteGCMconfiguration.ThefinalparttocompletethepictureistheGCMserver(infact,servers)hostedbyGooglethatperformthemessagequeuing,forwarding,andsoforth.

Torecap,thethreekeybuildingblocksforGCMareasfollows:

ClientApplication—Anapplicationyouwrite,suchasanAndroidapplication,thatsendsand/orreceivesmessagesfromaremoteservertohelpwithfunctionality.

GCMConnectionServers—Google’smessaginginfrastructure,whichmanagesallmessagingtraffic,messagequeuingintheeventofdeliverydelays,ultimatedeliveryguarantees,etc.

RemoteApplicationServer—Anapplicationyourwriteasaserver,hostedinanInternet-accessiblefashion,responsibleforsendingand/orreceivingmessagesfromclientapplications.

Wecanalsorepresentthisarchitectureinvisualform.Figure29-1showsthecomponentsandmessageflowinacompleteGCMsetup.

Figure29-1.GCMarchitecturaloverview

PreparingtoUseGCMinYourApplicationWherepreviouslywehavejumpedstraightintoJavacode,layoutXML,andsoforthwhenconstructingexampleapplications,forGCM-baseddevelopmentweneedtoundertakeafewpreparatorystepsinordertohaveGoogle’sserversaccepttrafficfromourclientandserver.

CreatingorConfirmingYourGCMProjectinGoogleDeveloperConsoleTouseanyofGoogle’sonlineservicesandAPIs,includingGCM,youwillneedtocreateanAPIProjectwithintheGoogleDeveloperConsole,atcloud.google.com/console.Youmightalreadyhaveaprojectyoucanreuse,butlet’sassumeyouarecreatingoneforthefirsttime.NavigatetotheconsoleURLandclicktheCreateProjectbutton.Followthepromptsforaccountandbillingdetails,etc.,andyoushouldendupwithanewproject(orconfirmanexistingone)asshowninFigure29-2.

Figure29-2.YourGoogleDeveloperConsolewithAPIProjectinPlace

ActivatingtheGCMAPIsforYourProjectWiththeAPIProjectinplace,younowneedtoactivatethespecificGCMAPIs.GooglesupportsdozensofseparateAPIs,allofwhicharedisabledbydefaulttoensureyoudonotaccidentallytriggerbehaviororincurcostsyouwerenotexpecting.ClickonyourAPIProject(inourcase,api-project-589435632025)andundertheAPIs&Authsectionontheleftside,selectAPIsandscrolluntilyouseeGoogleCloudMessagingforAndroid.TurnthisonusingtheEnableAPIbutton.YouwillknowyouhavesuccessfullyenabledtheGCMAPIwhenthebuttonchangesfromEnableAPItoDisableAPI,asshowninFigure29-3.

Figure29-3.YourGoogleDeveloperConsoleAPIProjectwithGCMEnabled

GeneratingYourAPIKeyAswithotherGoogleAPIs,accesstotheGCMAPIforyourprojectrequiresyourkey.Thishelpsensureeverythingfromtrafficseparation,soyourGCMmessagesarenotinadvertentlymixedwiththoseofotherapplications,throughtobilling,analytics,andsoforth.

Togenerateyourkey,choosetheCredentialsoptionunderAPIs&Auth.ChoosetheCreateNewKeyoption,andwhenpromptedforkeyproperties,selectServerforthekeytype.IfyouknowthepublicIPaddressofyourintendedserver,youcanusethatintheconfigurationsection,otherwiseyoucanuse0.0.0.0/0fortestingpurposes.ThenchooseCreatetohavethekeygenerated.Whenyouarereturnedtotheconsole,youshouldseeyourAPIkeyavailableundertheCredentialssubmenu.Notedownthekeyvalueasyouwillneeditshortly.

AuthenticatingGCMCommunicationTheAPIkeyisnottheonlypieceofidentifyinginformationusedtoauthenticateandauthorizemessagetransferwithinaGCMenvironment.YoucanfindmoredetailontheusesofGCMtokensandkeysonthedeveloper.google.comwebsite.Inbrief,thefollowingfourtokentypesareusedinyourGCMapplications:

SenderIDTheprojectIDcodeavailablefromtheGoogledeveloper

console.YourserverapplicationwillusethisaspartoftheregistrationprocesswithGoogle’sGCMserverstoenableittosendmessagestotheAndroidclientapplicationandyourusers.

SenderAuthTokenYourAPIkey,usedineverymessagesenttotheGCMserverstodemonstratetheauthenticityofthemessageanditsprovenancefromyourserverapplication.

ApplicationIDForyourAndroidapplication,thisisthefullyqualifiedJavapackagename,forinstancecom.androidbook.gcm.BecausethisisuniqueacrossallAndroidapplications,itallowstheGCMecosystemtoknowwhichapplicationsreceivewhichtypesofmessages.

RegistrationIDAllocatedtoyourclientAndroidapplicationwhenitregisterswiththeGCMserversformessagedelivery.TheregistrationIDissensitiveinformationandshouldbestoredsecurelyandnotdisclosed.

AlloftheseitemsincombinationallowclientandserverapplicationstoregisterwithGCMandbeidentifiedbyit,andalsotouniquelyidentifytheapplicationsandtheirmessages.

BuildinganAndroidGCM-EnabledApplicationBuildingameaningfulGCM-basedAndroidapplicationandthesupportingserver-sidethird-partyserviceisalargeundertaking—solargethatwecouldalmostwriteasmallbookjustonthattopic.Belowwewillcoverthemainconfigurationandcodingpointsyouneedtoconsiderwhenbuildingtheapplication,andyoucancheckthebookwebsiteforamorein-depthdiscussiononGCMwithfullexamples.

CodingtheClientComponentforGCMTheclientAndroidapplicationneedstoconsiderthreebroadareas.First,haveyourdevelopmentenvironmentsetupcorrectly.Second,configuretheAndroidprojecttoincludetherightdependenciesandprivileges.Lastly,writetheGCMregistrationmethodsandmessagehandlingmethodsintoyourJavacodeforyouractivity(oractivities).

ConfigureProjectDependenciesforYourProjectBeforewecanwritetheactualJavacodeandanyrelatedXMLlayoutforourdesiredGCM-basedapplication,weneedtoconfigureourprojecttohavethenecessaryAPIsavailableandinvokeyourIDE’sbuildtool(e.g.,gradle)withthenecessarydependenciestoensureasuccessfulbuild.

Yourdevelopmentenvironment(AndroidStudio,Eclipse,etc.)willneedtohavethe

GooglePlayServicesSDKinstalled.Double-checkthiswiththeSDKManagerfromtheIDEorcommandprompt.

Next,yourprojectwillneedtobeconfiguredtoworkwiththeGoogleCloudMessagingAPIprovidedbytheGooglePlayServicesSDK.Asanexample,toaddthistoanAndroidStudioproject,openyourproject’sbuild.gradlefileandensuretheAPIisincludedasadependency,asshowninListing29-1.

Listing29-1.build.gradlefileFragmentShowingPlayServicesDependency

dependencies{//yourotherdependenciesherecompile"com.google.android.gms:play-services:3.1.+"}

ForEclipseusers,theequivalenttaskistoaddgoogle-play-services.jarasanexternallibrarydependencytoyourproject,fromyourGooglePlayServiceslibrarycollection.Lastly,anyGCMapplicationmustrunonAndroid2.2(withPlayStoreinstalled)orlater.Updateyourmanifest’suses-sdkelementtosetandroid:minSdkVersiontoatleast8.

SettingManifestPropertiesforGCMOverandabovetheminimumSDKversionrequiredforGCM,yourapplicationwillrequirespecificpermissionsinordertodothefollowing:

RegisterwithGCMserverstoreceivemessages,usingcom.google.android.c2dm.permission.RECEIVEpermission.

Usethedevice’sinternetconnectiontosendmessages,usingandroid.permission.INTERNETpermission.

Exclusivelyreservemessagesintendedfortheapplicationandpreventotherapplicationsregisteringforthem.ThisusesthecustomC2D_MESSAGEpermissionblockwiththeapplicationnameprepended.

GCM-specificpermissionsforthereceiveryouwillalsodefine,sothattheGCMserversareallowedtosendmessagestoyourapplication.Thisusesthecom.google.android.c2dm.permission.RECEIVEsetting.

NoteTheearlierincarnationofGCMwasknownasC2DM,orCloudtoDeviceMessaging.ThusthereferencestotheearliernameofC2DMandC2D.

Thereceiveryoudefineshoulddeclareitsintent-filtertoactoncom.google.android.c2dm.intent.RECEIVEandusetheapplicationPackagenameasitscategory.

ItcanoftenbebettertoperuseasnippetofanexampleAndroidManifest.xmlfiletoseeallofthesesettingsinplace.Listing29-2showsexamplesettingsfromthefourkeypermissionsyourGCMapplicationwillrequire.

Listing29-2.SampleAndroidManifest.xmlEntriesforaGCMAndroidApplication

<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.gcm">

...

<uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="21"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.GET_ACCOUNTS"/><uses-permissionandroid:name="com.google.android.c2dm.permission.RECEIVE"/>

...

<permissionandroid:name="com.androidbook.gcm.permission.C2D_MESSAGE"android:protectionLevel="signature"/><uses-permissionandroid:name="com.androidbook.gcm.permission.C2D_MESSAGE"/>

...

<application…><receiverandroid:name=".GcmBroadcastReceiver"android:permission="com.google.android.c2dm.permission.SEND"><intent-filter><actionandroid:name="com.google.android.c2dm.intent.RECEIVE"/><categoryandroid:name="com.androidbook.gcm"/></intent-filter></receiver><serviceandroid:name=".GcmIntentService"/></application>...</manifest>

CodingYourMainActivitytoRegisterforGCM

BeforeyourapplicationcanreceivemessagesfromGCMservers(andyourserver-sideapplicationthatsendsthem),andbeforeitcansendmessagesofitsownbackthroughGCM,yourapplicationmustregisterwiththeGCMservers.ThisissotheGCMinfrastructureknowshowtorouteyourmessages,preventtrafficmix-ups,andsoon.Listing29-3showsanexampleactivityfragmentthatinitiatesregistrationintheonCreate()override.ThisexamplecodeismodeledonthegithubexampleGCMprojectGooglemakesavailableatdeveloper.android.com.

Listing29-3.RegisteringwithGCMfromJava

packagecom.google.android.gcm.demo.app;//importsfromadefaultactivity,andtheGCMspecificlibraries

publicclassGCMExampleActivityextendsActivity{

publicstaticfinalStringEXTRA_MESSAGE="message";publicstaticfinalStringPROPERTY_REG_ID="registration_id";privatestaticfinalintPLAY_SERVICES_RESOLUTION_REQUEST=9000;

StringSENDER_ID="a123b456c789d012";//RemembertouseyourID

TextViewmyMessageDisplay;GoogleCloudMessaginggcm;AtomicIntegermessageID=newAtomicInteger();Contextcontext;StringregistrationID;

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);

setContentView(R.layout.main);myMessageDisplay=(TextView)findViewById(R.id.display);context=getApplicationContext();

//RegisterwithGCMserversgcm=GoogleCloudMessaging.getInstance(this);finalSharedPreferencesmyAppPrefs=getGcmPreferences(context);registrationID=myAppPrefs.getString(PROPERTY_REG_ID,"");//youcouldalsoperformversionandotherchecksifdesired

if(registrationID.isEmpty()){//Notregistered,dosoasyncsoasnottoblockmainthreadtry{registerAppInBackground();}catch(NameNotFoundExceptione){//logdetailshere,assomethingfailedduringregistration}}}

privatevoidregisterAppInBackground(){newAsyncTask<Void,Void,String>(){@OverrideprotectedStringdoInBackground(Void…params){StringregStatus="Unregistered";try{if(gcm==null){gcm=GoogleCloudMessaging.getInstance(context);}registrationID=gcm.register(SENDER_ID);regStatus="RegisteredwithID:"+registrationID;//addyourcalltosecurelystoretheregistrationIDforlaterreuse//...}catch(IOExceptionex){//performyourerrorhandlinghere,e.g.retry}returnregStatus;}

}.execute(null,null,null);}...

Thisisagreatlysimplifiedexamplesoyoucanfocusontheabsolutelymandatorycomponentsandbuildfromthere.OuronCreate()methodfirstinstantiatesagcmobjectandaSharedPreferencesobject.Itthenretrievestheregistration_idfromthepreferences.Atthispointyourapplicationmaynotberegistered,whichwouldmeanthereturnedvaluefromthepreferenceswouldbeempty.Wetestforthisemptyvalue,andwhereanemptyregistration_idisdetectedweinitiateourregistrationprocessbyinvokingtheprivatemethodregisterAppInBackground().

TheimplementationofregisterAppInBackground()followsGoogle’srecommendationofperformingthefirst-timeregistrationasynchronously.Wedothisbecausewedon’twanttoblockthemainthreadwhilewewaitforthehandshakeandregistrationprocesstocomplete.Theprocesscouldpotentiallytakeseveralsecondsormore.Youcouldenhancetheapplicationherebyaddingarangeofintermediatestatusupdates,errorchecking,andmore.

Oncewehaveanapplicationthat’sregistered,wecancarryoutmessageexchange,andthenalltheotherlogicyoumightwantyourapplicationtohave,basedonordrivenbythemessagingaspect.Listing29-4showsanexamplemethodtosendamessagebasedonaBundleobjectconstructedtoholdyourmessagedetails.

Listing29-4.ExampleMessageSendingfromYourAndroidClient

privatevoidsendMessage(BundlemessagePayload){newAsyncTask<Void,Void,String>(){@OverrideprotectedStringdoInBackground(Void…params){Stringstatus="";try{Stringid=Integer.toString(messageID.incrementAndGet());gcm.send(SENDER_ID+"@gcm.googleapis.com",id,messagePayload);status="messagesent";}catch(IOExceptionex){//yourerrorhandlinghere//setstatusstring}returnstatus;}}}

ThelogicofthesendMessage()methodisentirelyconcernedwithsendingwhateveritisyouhaveconstructedinthemessagePayloadparameter.ThisBundleobjectislefttoyourimagination,butitcouldbetheinstantmessage,photo,voicemessage,orothercontentthatyourapplicationisactuallyhelpingtheuserwith.

WeonceagainuseAsyncTasktoensurewedon’tblockonmessagedelivery.Thisisauniversaldesignpatternwhenworkingwithanykindofmessagebusormessagedeliveryservice.Withintheasynchronouslogic,wegenerateauniquemessageidentifierwiththemessageID.incrementAndGet()methodandtheninvokethegcm.send()method,passingittheuniqueIDandourmessagepayload.

Errortrappingandretrylogicareeasytoaddatthispoint.IfyouaregoingtoassumeanyhighervaluetothemessageID(whichisnormallynotrecommended),itisbesttoplacetheretrylogicwithinthesendMessage()methodsoastobeabletoreusethemessage

IDgeneratedbeforeitfallsoutofscopeonmethodreturn.

CodingtheServerComponentforGCMYourthird-partyservicecanbewritteninbasicallyanylanguage,solongasitcanmakecallstotheGCMcloudendpointsandsupporttheauthorizationmessageprotocolswehavedescribedintheearliersectionsofthischapter.

BecausesuchservicesarenotstrictlyAndroidproductsorcode,wewillsavesomepreciouspagesfromthebookandpointyoutoexcellentexamplesthatGoogleprovidestogiveyouinspirationintowritingtheback-endservices.

Youcanreviewtheoptionsandapproachesforthisnon-Androidthird-partyserviceatdeveloper.android.com/google/gcm/server.html.

MovingBeyondtheGCMIntroductionSuchashortchaptercannotcovertheenormousbreadthofpossibilitiesandnuancesforGCM-basedapplications.Formoredetailsonwhatispossible,checkouttheAndroidStackExhangesite,android.stackechange.com,andthedeveloper.android.comsite.

Chapter30

DeployingYourApplication:GooglePlayStoreandBeyondCreatingagreatapplicationthatpeoplewillloveisonething,butyoualsoneedaneasywayforpeopletofindanddownloadit.GooglecreatedthePlayStoreforthispurpose.Fromaniconrightonthedevice,userscanclickstraightintothePlayStoretobrowse,search,review,anddownloadapplications.UserscanalsoaccessPlayStoreovertheInternettodothosesamethings,althoughthedownloadingisnottothecomputerbutratherappsaresentdirectlytotheuser’sdevice.Manyapplicationsarefree;forthosethatarenot,thePlayStoreprovidespaymentmechanismsforeasypurchasing.

ThePlayStoreisevenaccessiblefromintentsinsideofapplications,makingiteasyforapplicationstoreachouttothePlayStoretoguideusersintogettingwhattheyneedforyourapplicationtobesuccessful.Forexample,whenanewversionofyourapplicationbecomesavailable,youcanmakeiteasyfortheusertogostraighttothatPlayStorepagetogetorbuythenewversion.GooglePlayStoreisnottheonlywaytogetapplicationstodevices,however;otherchannelsareallovertheInternet.

TheGooglePlayStoreapplicationisnotavailablefromwithintheemulator(althoughhacksexisttomakeitavailable).Thismakesthingsalittlemoredifficultforadeveloper.IdeallyyouwillhaveadeviceofyourownthatyoucanusewithGooglePlayStore.Inthischapter,we’llexplorehowtogetyousetupforpublishingapplicationstothePlayStore;howtoprepareyourapplicationforsalethroughthePlayStore;howyoucanprotectyourselffrompiracy;howuserswillfind,download,anduseyourapplications;andfinally,alternativewaystomakeyourapplicationsavailable.

BecomingaPublisherBeforeyoucanuploadanapplicationtoGooglePlayStore,youneedtobecomeapublisher.Todoso,youmustcreateaGooglePlayPublisherAccount.Oncethat’sdone,youwillbeabletouploadyourapplicationstothePlayStoresotheycanbefoundanddownloadedbyusers.Ifyouwillbechargingmoneyforyourapp,oracceptingin-apppurchases,youwillalsoneedtosetupaGoogleWalletMerchantAccount.Googlehasmadetheprocesstogettheseaccountsrelativelypainlessandreasonablypriced.

Agoodplacetostartisthispage:http://developer.android.com/distribute/googleplay/start.htmlFromhereyoucanclickthebigStartbuttontobegintheprocess.Ifyoudon’talreadyhaveaGoogleAccountyouwillbepromptedtocreateone.Tobeapublisher,youwillalsoneedtoprovideadevelopername,ane-mailaddress,awebsiteaddress,andaphonenumberwhereyoucanbecontacted.Youwillbeabletochangethesevalueslater,onceyouraccountissetup.Youwillalsoneedtopaytheregistrationfee.Thisisdonevia

GoogleWallet.Inordertocompletethepaymenttransaction,youwillneedtouseyourGoogleaccount.

Oneoftheoptionspresentedtoyouduringthepaymentprocessis“Keepmyemailaddressconfidential.”ThisreferstothecurrenttransactionbetweenyouandGooglePlayStoretopurchasepublisheraccess.Ifyouchooseyes,you’llkeepyoure-mailaddresssecretfromGooglePlayStore.Thishasnothingtodowithkeepingyoure-mailaddresssecretfrombuyersofyourapplication.Buyers’abilitytoseeyoure-mailaddresshasnothingtodowiththisoption.Moreonthatlater.

NextupistheGooglePlayDeveloperDistributionAgreement(GPDDA).ThisisthelegalcontractbetweenGoogleandyou.Itspellsouttherulesfordistributingapps,collectingpayments,grantingrefunds,feedback,ratings,userrights,developerrights,andsoon.There’smoreontheseinthe“FollowingtheRules”sectionofthischapter.

UponacceptingtheAgreement,youwillbetakentoapagecommonlycalledtheDeveloperConsoleathttps://play.google.com/apps/publish/.

FollowingtheRulesTheGPDDAspellsoutalotofrules.Youmightwantlegalcounseltoreviewthecontractbeforeagreeingtoit,dependingonhowseriouslyyouplantooperatewithintheGooglePlayStore.Thissectiondescribessomehighlightsyoumightbeinterestedin:

YouhavetobeadeveloperingoodstandingtousetheGooglePlayStore.Thismeansyoumustgothroughtheprocessasdescribedtogetregistered,youmustaccepttheAgreement,andyoumustabidebytherulesintheAgreement.BreakingtherulescouldgetyoubarredandyourproductsremovedfromthePlayStore.

Youcandistributeproductsforfreeorforaprice.TheAgreementapplieseitherway.PaymentsmustbecollectedviaanauthorizedGooglePlayStorePaymentProcessor.ThisincludesGoogleCheckout(credit,debit,GooglePlaygiftcards),carrierbilling(e.g.,Verizon,AT&T),andPayPal.

Paidappswillincuratransactionfee,andpossiblyafeefromthedevicecarrier,tobedeductedfromthesaleprice.AsofMarch2015,thetransactionfeeis30percent,soifthesalepriceis$10,Googlecollects$3andyouget$7(assumingnocarrierfees).

ForEUcountries,Googleisrequiredtoremitthetaxesforyou.OutsideoftheEU,itisyourresponsibilitytoremitappropriatetaxestoyourtaxingauthorities.Forsomeofthosenon-EUcountries,youcanchoosetoletGoogleremitthetaxesforyou.Whenyousetupyourmerchantaccount,youspecifytheappropriatetaxratestoapplytopurchases.GoogleCheckoutwillcollecttheappropriatetaxesbasedonhowyousetupGoogleCheckout.ThismoneywillbeprovidedtoyouifGoogleisnotremittingforyou,andyoumust

remititappropriately.ForadditionalinformationonsalestaxesintheUnitedStates,tryhttp://biztaxlaw.about.com/od/businesstaxes/f/onlinesalestax.htmandwww.thestc.com.

Youareallowedtodistributeafreedemoversionofyourapplication,withanoptiontopaytounlocktheapplication’sfullsetoffeatures;however,youmustcollectthepaymentviaanauthorizedGooglePlayStorePaymentProcessor.Youarenotallowedtoredirectusersofyourfreeapplicationtosomeotherpaymentprocessortocollectupgradefees.Youcouldthinkofitthisway:ifyou’remakingmoneyviaGooglePlayStore,Googlewantsitsshare.

In-appbillingallowsanapplicationtochargefordigitalgoodsorassetsusedwithintheapplication.Adigitalassetcouldbesomethinglikeavirtualweaponornewlevelsforagame,oramusicorgraphicsfile.Thecheckoutprocessisthesameasforpurchasingapplications.

Ifyourapplicationrequiresausertohavealoginonawebserversomewhere,andthatwebserverchargestheuserasubscriptionfee,thatwebservercouldcollectthesubscriptionfeeanywayitwantsto.Inthisway,youhavedisconnectedthesubscriptionfeefromtheapplication,andit’sOKbyGoogletomaketheapplicationavailableinGooglePlayStore—aslongasyourfreeapplicationisnotdirectinguserstothewebsite.SomepeoplejustdecidetodistributetheirfreeAndroidappfromthesamewebserverastheservice,butthisdoesrequiretheusertoenableinstallationofappsfromunknownsources,whichcandiscouragesomeusersfrominstalling.

Itseemsthatyoucanusealternatepaymentprocessorstoacceptdonationsfromusersofyourfreeapp,butyoucannotcreateincentiveswithinyourapptoencouragethosedonations.

WhiletheGPDDAsaysrefundscanberequestedupto48hoursafterpurchase,asofMarch2015refundscanberequestedbytheuserupto2hoursafterpurchasingforanautomaticrefund.Refundsarenotgiventouserswhocanpreviewtheproductpriortodownload.Thisincludesringtonesandwallpapers.

GoogleCheckout,however,doesallowthedevelopertoissuearefundeveniftherefundwindowhaspassed.TheusercangototheirGooglePlayactivityhistory,andfromtherecanrequestarefundevenwellaftertheinitial2hours.Ifitislessthan48hoursfromthepurchase,therefundwillprobablybeautomatic.Otherwise,itisuptothedeveloperwhetherornottoreturnanymoney.

Youarerequiredtoprovideadequatesupportforyourproduct.Ifadequatesupportisnotprovided,userscanrequestrefundsthroughGoogle,andthesewillbechargedbacktoyou,possiblyincluding

handlingfees.

UsersgetunlimitedreinstallsofapplicationsdownloadedfromtheGooglePlayStore.Ifauserdoesafactoryresetoftheirdevice,thisfeatureallowsthemtogetalltheirappsbackwithouthavingtorepurchase.

Developersagreetoprotecttheprivacyandlegalrightsofusers.Thisincludesprotecting(securing)anydatathatmightbecollectedintheprocessofusingtheapplication.Itispossibletochangetherulesregardingusers’dataprotection,butonlybydisplayingandhavingtheuseracceptaseparateagreementbetweenyouandthatuser.

YourapplicationmustnotcompetewiththeGooglePlayStore.GoogledoesnotwantanapplicationfromwithinGooglePlayStoretosellAndroidproductsfromoutsideGooglePlayStore,thusbypassingitspaymentprocessor.Thisdoesnotmeanthatyoucan’talsosellyourapplicationthroughotherchannels,butyourapplicationonGooglePlayStorecannotitselfbedoingthesellingofAndroidproductsoutsideofGooglePlayStore.

Googlewillassignproductratingstoyourproducts.Theratingscouldbebasedonuserfeedback,installrates,uninstallrates,refundrates,and/oraDeveloperCompositeScore.TheDeveloperCompositeScoremaybecalculatedbyGoogleusingpasthistoryacrossapplications,andthiscouldinfluencetheratingofnewapplications.Forthisreason,itisimportanttoreleasegood-qualityapplicationsassociatedwithyou,eventhefreeones.It’snotclearthattheDeveloperCompositeScoreevenexists,butifitdoesthere’snowaytoseeyours.

BysellingyourapplicationthroughGooglePlayStore,youaregrantingtheusera“non-exclusive,worldwide,perpetuallicensetoperform,displayandusetheProductonthedevice.”However,itisquitealrightforyoutowriteaseparateEndUserLicenseAgreement(EULA)thatsupersedesthisstatement.MakethisEULAavailableonyourwebsite,orprovideanotherwayforshoppersanduserstobeabletoreadit.

GooglerequiresthatyouabidebythebrandingrulesforAndroid.TheseincluderestrictionsontheuseofthewordAndroid,aswellasuseoftherobotgraphic,logo,andcustomtypeface.Formoredetails,gotohttp://developer.android.com/distribute/tools/promote/brand.html

DeveloperConsoleTheDeveloperConsoleisyourlandingpageforcontrollingyourapplicationsinGooglePlayStore.FromtheDeveloperConsole,youcansetupamerchantaccountinGoogle

Checkout(soyoucanchargeforyourapplications),uploadapplications,andgetinformationaboutyouruploadedapplications.Youcanalsoedityouraccountdetailsincludingdevelopername,e-mailaddress,webaddress,andphonenumber.Figure30-1showstheDeveloperConsole.

Figure30-1.TheGooglePlayStoreDeveloperConsole

Ifyoudonotsetupamerchantaccount,youwillbeunabletochargeforyourproductsinGooglePlayStore.Settingupamerchantaccountisnotdifficult.ClickthelinkfromtheDeveloperConsole,fillouttheapplication,agreetotheTermsofService,andyou’reallset.YouwillneedtoprovideaUSFederaltaxID(EIN),acreditcardnumberplusaUSSocialSecurityNumber(SSN),orjustacreditcardnumber.Thetaxinformationisusedtoverifyyourcreditstatustoensuretimelydeposits.ThecreditcardinformationisusedtohandlechargebacksduetobuyerdisputeswhenthereareinsufficientfundsinyourGoogleCheckoutaccount.Youcanalsosupplybankaccountinformationtoenableelectronicfundstransfersfromtheproceedsofyoursales.

NotethatGoogleCheckoutisaserviceformorethanjustGooglePlayStore.Therefore,donotgetconfusedbythetransactionfeeinformationforGoogleCheckoutfornon–GooglePlayStoresales.The30percentmentionedpreviouslyisthetransactionfeerateforGooglePlayStore.ThereisalsoadditionalGoogleCheckouttransactionfeeinformationfornon–GooglePlayStoresales,andthosedonotapplytoGooglePlayStore.

UploadingandmonitoringyourapplicationsareprobablythemainfunctionsoftheDeveloperConsolethatyouwilluse,althoughtheConsoleisalsowhereyoucansignupforaccesstoGoogleAPIsandgameservicesandlinktoyourAdWordsaccount(s).

Formonitoring,thePlayStoreprovidestoolstoseehowyourapplicationisdoingintermsoftotaldownloadsandhowmanyusersstillhaveitinstalled.Youcanseetheoverallratingofyourappsintermsof0to5stars,andhowmanypeoplehavesubmittedarating.Therearevariousreports,charts,andgraphsintheDeveloperConsolesoyoucanseehowyourapplicationisdoingindifferentversionsofAndroid,ondifferentdevices,indifferentcountries,andindifferentlanguages.

Userscansubmitcommentsinadditiontoratingyourapplication.Itisinyourbestinteresttoreadthecommentsinordertoaddressanyproblemsquickly.Includedwithacommentistheuser’sratingofyourapp,anameoftheuserastypedbythem,andthedatethecommentwassubmitted.Youareabletoreplytospecificcommentsandthatuserwillreceiveane-mailtoletthemknow.Youcanonlyleaveonereplyperreview,butyoucanalwaysedityourreplylater.Inanextremecase,whereacommentisparticularlyharmfulorinappropriate,youcancontactGooglesupportbystartinghereattheHelp

Centerwebsite:https://support.google.com/googleplay/android-developer/.

TheDeveloperConsoleallowsyoutorepublishyourapplication—forupgrades,forexample—ortounpublishtheapplication.Unpublishingdoesnotremoveitfromdevices,nordoesitevennecessarilyremovetheappfromtheGoogleservers,especiallyifit’sapaidapp.Auserwhohaspaidforyourapplicationandwhohasuninstalledit,butnotrequestedarefund,isallowedtoreinstallitlaterevenifyou’veunpublishedit.TheonlywayitistrulyunavailabletousersisifGooglepullsitduetoaviolationoftherules.

Youcanalsolookaterrorsthatweregeneratedbyyourapplicationandseeapplicationfreezesandcrashes.Figure30-2showstheCrashes&ANRsscreen.

Figure30-2.TheCrashes&ANRsscreen

Drillingintothedetailsofacrashreport,youcanseethestacktraceofthecrash,aswellaswhichtypeofdevicewasrunningtheapplicationandthetimeofthecrash.Unfortunately,youcannotcommunicatebacktotheuserwhoexperiencedtheproblemtogetadditionaldetailsortohelpthemgettheissueresolved.Youhavetohopethattheaffecteduserswillgetintouchwithyouthroughcomments,e-mail,oryourwebsite.Otherwise,you’lljusthavetofigureoutfromthecrashreportwhatwentwrongandtrytofixit.

Ifyoureallywanttoknowhowausergottoacrash,you’llwanttoimplementoneofthemobileanalyticspackagesintoyourapp.Thesewillgenerateeventrecordsasauserstepsthroughyourapplication,andwillalsoreportcrashes.Thebreadcrumbs(eventrecords)willletyouknowthestepsausertookuptothepointofthecrash.ThiscapabilityisseparatefromtheGooglePlayStore,however.

There’sonemorefeatureoftheDeveloperConsoleyoumayneedtouse:theHelpportionofthewebsite.TheHelpbuttonisintheupper-rightcorner.Clickingitshowsyousomeinlinehelp,butalsohasalinktotheHelpCenterwebsite.Therearealsolinksfor

submittinge-mailorforanonlinechat(duringbusinesshours).

We’venowintroducedyoutosomeofthenicefeaturesoftheDeveloperConsole,butyouprobablywanttogetintothemostusefulpart,whichisgettingyourapplicationsintotheGooglePlayStoresouserscanfindthemanddownloadthem.Butbeforewedothat,let’sgooverhowtoprepareyourapplicationforuploadandsale.

PreparingYourApplicationforSaleTherearequiteafewthingstothinkaboutanddototakeanapplicationfromcodecompletetoGooglePlayStore.Thissectionwillhelpyouthroughthoseitems.

TestingforDifferentDevicesWithmoreandmoreAndroiddevicesbecomingavailable,andeachonepotentiallyhavingsomenewhardwareconfiguration,itisveryimportantthatyoutestforavarietyofdevicesyouwanttosupport.Youcouldpurchasesomeofthedevicesthatyouwanttosupport,butyouprobablycan’tpurchasethemall.TherearesomeonlineservicesthatmakerealdevicesavailableovertheInternet.YourotheroptionistoconfigureAndroidVirtualDevices(AVDs)foreachtypeofdevice,specifytheappropriatehardwareconfiguration,andthentestwiththeemulatorandeachAVD.SomedevicemanufacturersmakeAndroidemulatorpackagesavailablethatarespecifictotheirdevices,socheckouttheirwebsitesfordownloadoptions.

TheAndroidSDKprovidesvariousclassestoassistwithtesting,aswellastheUI/ApplicationExerciserMonkey.Thesetoolswillhelpyoudoautomatedtestingsoyoudon’tspendforevertestingyourapplicationmanually.Seethesewebpagesformoredetails:

https://developer.android.com/tools/testing/index.htmlhttps://developer.android.com/tools/testing-support-library/index.html

Beforeyoubegintesting,youprobablywanttoremovefromyourcodeanydevelopmentartifactsthatyounolongerneed,andalsoanydevelopmentartifactsfrom/res.Youwantyourapplicationtobeassmallaspossibleandtorunasquicklyaspossiblewiththeleastamountofmemory.Finally,besuretodisableorremoveanydebuggingfeaturesfromyourapplicationthatyoudon’twantdistributedtoproduction.

SupportingDifferentScreenSizesAndroidsupportsmanyscreensizes.Inordertorunonthesmallestsize,youmustsetaspecific<supports-screens>elementasachildelementof<manifest>withintheAndroidManifest.xmlfile.Withoutthistagspecifyingthatyourapplicationsupportsthesmallscreensize,yourapplicationwillnotbevisibleinthePlayStoretodevicesthathaveasmallscreen.

Tosupportdifferentscreensizes,youmayneedtocreatealternateresourcefilesunder/res.Forexample,forfilesin/res/layout,youmayneedtocreatecorrespondingfilesin/res/layout-smalltosupportsmallscreens.Thisdoesnotmeanyoumustalsocreatecorrespondingfilesin/res/layout-largeand/res/layout-normal,sinceAndroidwilllookin/res/layoutifitcan’tfindwhatitneedsinamorespecificresourcedirectorysuchas/res/layout-large.Remember,too,thatyoucanhavecombinationsofqualifiersfortheseresourcefiles;forexample,/res/layout-small-landwouldcontainlayoutsforsmallscreensinlandscapemode.Supportingsmallscreensprobablymeanscreatingalternateversionsofdrawablessuchasicons,too.Fordrawables,youmayneedtocreatealternateresourcedirectories,takingintoaccountscreenresolutionaswellasscreensize.

Tabletsofcoursegointheoppositedirectionintermsofscreensize,usingthelabelxlarge.Thesame<supports-screens>tagasbeforeisusedtospecifyifyourapplicationwillrunonextra-largescreens,andtheattributetouseinsideofthistagisandroid:xlargeScreens.Insomecases,youmayhaveatablet-onlyapplication,inwhichcaseyouwouldspecificallyindicatethatfortheothersizes,theattributevalueisfalse.

PreparingAndroidManifest.xmlforUploadingYourAndroidManifest.xmlfilemayneedtobetweakedalittlebitbeforeyoucanuploadittoGooglePlayStore.ADTnormallyputstheandroid:iconattributeinthe<application>tag,andnotin<activity>tags.Ifyouhavemorethanoneactivitythatcanbelaunched,you’llwanttospecifyseparateiconsforeachactivitysotheusercanmoreeasilytellthemapart.Butyou’llstillneedaniconspecifiedin<application>,whichalsoservesasthedefaultactivityiconforanyactivitiesthatdon’tspecifytheirownicon.Yourapplicationwillworkfineondevicesandintheemulatorwiththeandroid:icononlyspecifiedinthe<activity>tags,butwhenGooglePlayStoreinspectsyourapplication’s.apkfilewhenuploading,itlooksforiconinformationinthe<application>tag.

GooglePlayStorepreventsuploadingyourapplicationifthepackagenameyou’veusedstartswithcom.google,com.android,android,orcom.example,butwehopeyoudidn’tuseoneofthoseinyourapplication.

Therearemanyothercompatibilitiestoconsiderforyourapplication.Somedeviceshavecameras,somedon’thavephysicalkeyboards,andsomehavetrackballsinsteadofdirectionalpads.Use<uses-configuration>and<uses-feature>tagsinyourAndroidManifest.xmlfileasneededtodefinewhathardware/platformrequirementsyourapplicationhas.GooglePlayStorewillenforcethisandnotletyourapplicationbeshowntoauseronadevicethatwon’tsupportyourapplication.Notethatthesetagsaredifferentandseparatefromthe<uses-permission>tagsoftheAndroidManifest.xmlfile.Inmostcases,youwouldendupwithbothtagsinyourAndroidManifest.xmlfile,forspecifyingthatacameraisrequired,andforspecifyingthatpermissiontousethecameraisrequired.Butnotallfeaturesrequire

permission,soitisinyourbestinteresttospecifythefeaturesyouneed.

Thereisanotherbigdifferencebetween<uses-permission>and<uses-feature>:the<uses-feature>tagcansaythatyourapplicationrequiresthatfeatureorthatyourapplicationcanfunctionwithoutit.Thatis,thereisanattributecalledandroid:requiredthatcanbesettoeithertrueorfalse;bydefaultit’strue.Ifthereisapermissionforafeature,butyoudon’tsupplythecorresponding<uses-feature>tag,thenbydefaultit’sasifyouspecified<uses-feature>andthefeatureisrequired.Forexample,yourapplicationmaytakeadvantageofBluetoothifit’savailable,butwillworkjustfineifitisnot.Therefore,inthemanifestfile,inadditiontotheBluetoothpermissionelement,you’dhavesomethinglikethis:

<uses-featureandroid:name="android.hardware.bluetooth"android:required="false"/>

Withinyourapplication’scode,youshouldmakeacalltothePackageManagertofindoutifBluetoothisavailableornot,whichyoucoulddowiththefollowing:

booleanhasBluetooth=getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);

ThentakeappropriateactioninyourapplicationifBluetoothisnotthere.TheAndroiddocumentationcanbeconfusinginthisarea.IfyoulookattheDeveloperGuidepagefor<uses-feature>,youwillnotseeasmanyfeaturesasaredescribedonthePackageManagerreferencepage,whichdefinesaFEATURE_*constantforeachavailablefeature.

The<uses-configuration>tagisalittledifferent.Itspecifieswhatsortofkeyboard,touchscreen,and/ornavigationalcontrolsthedevicemusthave.Butinsteadofbeingindependentchoicessuchas<uses-feature>,youwouldputthecombinationsofconfigurationchoicestogetherintowhatyourapplicationrequires.Forexample,ifyourapplicationrequiresafive-waynavigationcontrol(thatis,aD-padoratrackball)andatouchscreen(usingeitherastylusorafinger),youwouldspecifytwotagsasfollows:

<uses-configurationandroid:reqFiveWayNav="true"android:reqTouchScreen="stylus"/><uses-configurationandroid:reqFiveWayNav="true"android:reqTouchScreen="finger"/>

LocalizingYourApplicationIfyourapplicationwillbeusedinothercountries,youmightwanttoconsiderlocalizingit.Thisisrelativelyeasytodotechnically.Findingsomeonetodothelocalizingisanothermatter.Fromthetechnicalpointofview,yousimplycreateanotherfolderunder/res—forexample,/res/values-frtoholdaFrenchversionofstrings.xml.Takeyourexistingstrings.xmlfile,translatethestringvaluestothenewlanguage,andsavethenewtranslatedfileunderthenewresourcefolderusingthesamefilenameastheoriginal

file.Atruntime,ifthedevice’slanguageissettoFrench,Androidwilllookforstringsthatwereplacedunder/res/values-fr.Ifitcan’tfindstringsfromthere,itwillthenlookforstringsfrom/res/values.

Thesametechniqueworksfortheothertypesofresourcefiles—forexample,drawablesandmenus.Imagesandcolorsmayworkbetterforyourusersiftheyaredifferentfordifferentcountriesorcultures.Forthisreason,itisagoodideatonotusetruecolornamesforyourresourcenamesforcolors.Intheonlinedocumentationforcolors,itiscommontoseesomethinglikethis:

<colorname="solid_red">#f00</color>

Thismeansthatinyourcodeorotherresourcefiles,you’rereferringtothecolorbytheactualnameofthecolor,inthiscase,solid_red.Inordertolocalizethecolortosomethingmoreappropriatefortheothercountryorculture,itwouldbebettertouseacolornamesuchasaccent_color1oralert_color.InEnglish,redmightbetheappropriatecolorvaluetouse,whileinSpanishitmightbebettertouseashadeofyellow.Becauseacolornamelikealert_colordoesnotrevealtheactualcolorthatyou’reusing,itislessconfusingwhenyouwanttochangetheactualcolorvaluetosomethingelse.Atthesametime,youcandesignapleasingcolorscheme,withbasecolorsandaccentcolors,andbemoreconfidentthatyou’reusingthecorrectcolorsinthecorrectplaces.

Menuchoicesmightneedtobechangedindifferentcountries,usingfewerormoremenuitems,orbeorganizeddifferently,dependingonwheretheapplicationisbeingused.Menusaretypicallystoredunder/res/menu.Ifyouarefacedwiththissituation,youareprobablybetteroffputtingallyourstringtextintostrings.xml,orotherfileslocatedunderthe/res/valuesdirectory,andusingstringIDsintheappropriateresourcefileseverywhereelse.Thismakesitfarlesslikelythatyouwillmisstranslatingastringvalueinsomeobscureresourcefile.Yourlanguagetranslationworkisthenlimitedtothefilesunder/res/values.

PreparingYourApplicationIconShoppersandyouruserswillseeyourapplication’siconandlabelprominentlyinbothGooglePlayStoreandontheirdeviceoncethey’vedownloadedit.Pleasetakespecialcaretocreategoodiconsandgoodlabelsforyourapplicationanditsactivities.Localizethemasnecessaryordesired.Andrememberthatfordifferentscreensizes,youriconsmayneedtobetweakedtolookgood.Checkoutwhatotherdevelopershavedonewiththeiricons,especiallythoseapplicationsinthesamecategoryasyourapplication.Youwantyourapplicationtogetnoticed,soit’sbetternottoblendinwithalltheothers.Atthesametime,youwantyouriconandlabeltoworkwellonadevicewhensurroundedbylotsofotherapplicationiconsthatdootherthings.Youdon’twantausertobeconfusedaboutwhatyourapplicationdoes,somaketheiconrepresentativeofthefunctionalityofyourapplication.

Whencreatinganyimageforyourapplication,butespeciallyyouricon,youneedto

considerthescreendensityofthetargetdevice.Densitymeansthenumberofpixelsperinch.Don’tthinkthatasmallscreenislowdensityandalargescreenishighdensity—youcouldseeanycombinationofsizeanddensity.Forahigh-densityscreen,youwillprobablychooseaniconwith72×72pixels.Themedium-densityiconwillusuallybeofsize48×48pixels.Andforextra-highdensity,it’s96×96pixels.Foralow-densityscreen,makinganiconappeartobetherightsizemeansmakingtheiconwithfewerpixels,typically36×36.Androidhelpsyouinthelow-densitycasebecauseitwillautomaticallydownscaleyourHDPIiconbyhalf,soyoudon’tneedtoprovidealow-densityiconyourself.Ingeneral,you’llfinditeasiesttoonlyworryaboutdensityforimagessuchasicons.You’llworryaboutscreensizewhendefininglayouts.

DirectingUsersBacktothePlayStoreAndroidhasaURIschemetohelpfacilitatefindingapplicationsinGooglePlayStore:market://.[GooglePlayStorewasformerlycalledAndroidMarket.]Forexample,ifyouwanttodirectyouruserstothePlayStoretolocateaneededcomponent,ortoupselltoanadditionalappthatunlocksfeaturesinyourapplication,youwoulddosomethingasshownhere,whereMY_PACKAGE_NAMEwouldbereplacedbyyourrealpackagename:

Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse("market://search?q=pname:MY_PACKAGE_NAME"));startActivity(intent);

ThiswilllaunchthePlayStoreapponthedeviceandtaketheusertothatpackagename.Theusercanthenchoosetodownloadorbuytheapplication.Notethatthisschemedoesnotworkinanormalwebbrowser.Inadditiontosearchingusingpackagename(pname),youcansearchbydevelopernameusingmarket://search?q=pub:\“FnameLname\”oragainstanyofthepublicfields(applicationtitle,developername,andapplicationdescription)inGooglePlayStoreusingmarket://search?q=<querystring>.

TheAndroidLicensingServiceThewaythatAndroidappsareconstructedunfortunatelymakesthemtargetsforpiracy.ItispossibletomakecopiesofAndroidappsthatcanthenbedistributedtootherdevices.Sohowcanyouensurethatuserswhohavenotpurchasedyourapplicationcannotrunit?TheAndroidteamhascreatedsomethingcalledtheLicenseVerificationLibrary(LVL)tomeetthisneed.Here’showitworks.

IfyourapplicationwasdownloadedviaGooglePlayStore,thentheremustbeacopyoftheGooglePlayStoreapponthedevice.Inaddition,theGooglePlayStoreapphaselevatedpermissionstobeabletoreadvaluesfromthedevicesuchastheuser’sGoogleaccountname,theIMSI,andotherinformation.TheGooglePlayStoreappwillrespondtoalicenseverificationrequestfromanapplication.YoumakecallsintotheLVLfromyourapplication,LVLcommunicateswiththeGooglePlayStoreapp,theGooglePlay

StoreappcommunicateswithGoogleservers,andyourapplicationgetsananswerbackindicatingwhetherornotthisuseronthisdeviceislicensedtouseyourapplication.ThismeanstheappmusthavebeenpurchasedthroughGooglePlayStore;otherwisetheGoogleserverswon’tknowaboutit.Therearesettingsunderyourcontroltodecidewhattodoifthenetworkisunavailable.AfulldescriptionoftheprocessofimplementingLVLcanbefoundathttps://developer.android.com/google/play/licensing/index.html

Onethingtobeawareof,though,isthattheLVLmechanismissubjecttohacking.Ifsomeonecangettoyourapplication’s.apkfile,theycandisassembletheappandthenpatchitiftheyknowwheretolookforthereturnvaluefromtheLVLcall.IfyouusetheobviouspatternofaswitchstatementaftergettingtheresponsefromLVL,tobranchtotheappropriatelogicbasedonthereturncode,ahackercansimplyforceasuccessfulreturncodevalue,andtheyownyourapp.Forthisreason,theAndroidteamhighlyrecommendsthatyouimplementobfuscationofyourapptohidethepartofyourapplicationwhereyoucheckthereturncodefromLVL.Thisgetsfairlycomplicated,asyoucanimagine.

UsingProGuardforOptimization,FightingPiracyGoogleprovidessomesupportforobfuscationintheformoftheProGuardfeature.ProGuardisnotaGoogleproduct,buthasbeenintegratedintoADTandAndroidStudiosoit’seasytouse.ProGuarddoesmorethanjustprovideobfuscationforfightingpiracy;italsomakesyourapplicationsmallerandfaster.Itdoesallthisbystrippingoutdebugginginformation,cuttingoutcodethatwillneverrun,andchangingnames(ofclasses,methods,andsoon)tomeaninglessstrings.Examplesofcodethatwillneverrunincludelibraryclassesandmethodsthatarenevercalled,andloggingthatdependsonaconstantthatyousettofalse(forproduction).Itcanalsorecognizeoptimizationssuchasbinary-shiftingavalueleftbyonebitpositioninsteadofmultiplyingitby2.Bystrippingoutdebugginginformationandchangingthenames,theresultingcompiled.apkfilewon’trevealvariablenames,classnames,methods,andsoon,soitbecomesextremelydifficulttofigureoutwhatthecodedoesandthereforehowtostealit,modifyit,andreleaseitassomethingelse.

Whenyoucreateyourapplication,itshouldautomaticallygetaproguard-project.txtfile.ThedefaultfilewilllooksomethinglikeListing30-1.

Listing30-1.Sampleproguard-project.txtFile

#ToenableProGuardinyourproject,editproject.properties#todefinetheproguard.configpropertyasdescribedinthatfile.##AddprojectspecificProGuardruleshere.#Bydefault,theflagsinthisfileareappendedtoflagsspecified

#in${sdk.dir}/tools/proguard/proguard-android.txt#YoucanedittheincludepathandorderbychangingtheProGuard#includepropertyinproject.properties.##Formoredetails,see#http://developer.android.com/guide/developing/tools/proguard.html

#Addanyprojectspecifickeepoptionshere:

#IfyourprojectusesWebViewwithJS,uncommentthefollowing#andspecifythefullyqualifiedclassnametotheJavaScriptinterface#class:#-keepclassmembersclassfqcn.of.javascript.interface.for.webview{#public*;#}

Youalsoneedtouncommenttheproguard.configpropertyintheapplication’sproject.propertiesfiletothelocationoftheproguard-project.txtfile.Thelinelookslikethis:

proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

Asyoucansee,thereisastocksetofProGuardconfigurationsprovidedtoyoubyafileunderthetools/proguarddirectoryoftheAndroidSDK.YoucanthenaugmenttheProGuardconfigurationintheproguard-project.txtfileaspartofyourapplicationproject.Notethattheprovidedconfigurationdoesnotinfactenableoptimizations,astheserequiremoretestingtobesurethatyourapplicationstillworkscorrectly.Ifyouwanttotryoptimizations,changethereferenceintheproject.propertiesfileto${sdk.dir}/tools/proguard/proguard-android-optimize.txt.

Asmentioned,ProGuarddoesitsworkbystrippingstuffout.Sometimesitstripsouttoomuch,andthatiswhyyouseethe-keepoptionsspecifiedintheproguard-android.txtfile.Whenyouproducean.apkfile,youneedtotestittomakesureProGuarddidn’ttakeouttoomuch.Ifyoufinderrorsduetomissingclassesormethods,youcanedittheproguard-project.txtfiletoincludeanother-keepoptionfortheitemyou’remissing.Rebuildyour.apkfile,andtestagain.WerecommendusingtheExportSignedApplicationPackageoptionundertheAndroidToolsmenuoptioninEclipse,becauseitwilltakecareofcallingProGuardforyouasitbuildsthe.apkfile.Exportingiscoveredinthenextsection.

YoucanalsoconfigureAnttoobfuscateusingProGuardifyouuseAnttodoyourbuilds.

WhenProGuarddoesitsthing,you’llgetafilecalledmapping.txtalongwithyour.apkfile.Hangontothisfilebecauseyouwillneedittode-obfuscateastacktracefromyourapplication.IfyouuseEclipsetoexportyour.apkfile,youwillseeanewproguarddirectorycreatedwithinyourEclipseproject.Themapping.txtfilewillbeinthere.Thecommandtouseisretrace,andit’slocatedintheAndroidSDKdirectoryundertools/proguard/bin.Theargumentstoretraceincludethemapping.txtfileandthestacktracefile,butbeawarethatyouneedtospecifythefullpathnametoeach.Also,youshouldkeeptrackofwhichversionofyourapplicationgoeswithwhichmapping.txtfile.

Onemorecautionabouttestingyourapplication.AndroidKitKatintroducedanexperimentalruntimeenginecalledtheAndroidRunTime(ART),andinLollipopitbecametheoneandonlyruntimeengine.Youshouldtestyourapplicationwithboth,especiallyifyouuseProGuardanddooptimizations.

PreparingYour.apkFileforUploadingTogetyourtestedapplicationreadyforuploading—thatis,tocreatethe.apkfiletoupload—youneedtocreateasignedexportofyourapplication.Thiscanbedoneanumberofways,butthesimplestaretousethebuilt-inIDEfeatures.ForEclipseyouwouldright-clickontheprojectnameandchooseAndroidTools ExportSignedApplicationPackage….ForAndroidStudio,youwouldselecttheprojectnameandchoosetheBuildmenu GenerateSignedAPK…FollowthedialogstochooseapropersigningcertificatekeyandcreateyourproductionAPK.

UploadingYourApplicationUploadingiseasytodobuttakessomepreparation.Beforeyoubeginanupload,therearesomethingsyouwillneedtohavereadyanddecisionsyouhavetomake.Thissectioncoversthatpreparationandthosedecisions.Then,whenyou’vegoteverythingyouneed,gototheDeveloperConsoleandchoose+Addnewapplication.You’llbepromptedtosupplylotsofinformationaboutyourapplication,thePlayStorewillrunsomeprocessingonyourapplicationandtheinformation,andthenyourapplicationwillbereadytopublishtothePlayStore.

Theprevioussectioncoveredpreparingyourapplication.apkfileforuploading.Makingyourapplicationattractivetoshoppersrequiressomemarketingonyourpart.Youneedgooddescriptionsofwhatitisanddoes,andyouneedgoodimagessoshoppersunderstandwhattheymightdownload.

TheGooglePlayStoreunderstandsthatyoucouldmarketyourapplicationindifferentcountries.Therefore,youhavetheabilitytoprovidetextandgraphicslocalizedforthedifferentcountrieswithjustoneapplication.

Graphics

You’llbeaskedtouploadscreenshotsforyourapplication.TheeasiestwaytocapturescreenshotsofyourapplicationistouseDDMS.FireupEclipse,launchyourapplicationintheemulatororonarealdevice,andthenswitchEclipseperspectivestoDDMSandtheDeviceview.FromwithintheDeviceview,selectthedevicewhereyourapplicationisrunningandthenclicktheScreenCapturebutton(itlookslikealittlecameraintheupper-rightcorner)orchooseitfromtheViewmenu.Ifyouhaveachoicewhensaving,choose24-bitcolor.TheAndroidDeviceMonitorisverysimilartoDDMSandisavailableasastand-alonetool(calledmonitor)fromundertheSDKtoolsdirectory,orfromtheToolsmenuofAndroidStudio.

GooglePlayStorewillconvertyourscreenshotstocompressedJPEG;startingwith24-bitwillproducebetterresultsthanstartingwith8-bitcolor.Choosescreenshotsthatwillmakeyourapplicationstandoutfromtherestbutthatalsoshowtheimportantfunctionality.Youmustsupplyatleasttwoscreenshots,andyoucanprovideuptoeight.Beawarethatyouhavetheabilitytouploadscreenshotsforyourapplicationforotherlanguages.Ifyourapplicationhasbeenlocalizedforanothercountryand/orlanguage,you’llwantthescreenshotstocorrespond.

Nextupisahigh-resapplicationicon.Thiscouldbetheexactsamedesignasyourapplicationicon,butGooglePlayStorewantsa512×512pixeliconimage.Thisisrequired.

Thefeaturegraphicisrequiredandisalarge1024×500pixelsinsize.ThisgraphicisusedintheFeaturedsectionofGooglePlayStoresoyouwantthistolookreallygood.

Youcanprovideapromotionalgraphicaswell,butitssizeissmallerthanascreenshot.Althoughthisgraphicisoptional,itisagoodideatoincludeit.Youneverknowwhenthegraphiccouldbedisplayed;withoutone,youdon’tknowwhatwillbedisplayedinitsplace,ifanything.OneplacethePromoGraphicappearsisatthetopofyourapplication’sDetailspageinGooglePlayStore.

Bythetimeyoureadthis,therecouldbeothergraphicsyoucouldupload.Forexample,GooglenowacceptsaTVBannergraphicforappsthatwouldbeviewedonaTV.

ThelastbitofgraphicsrelatedtoyourapplicationisanoptionalvideothatyoucanputoutonYouTubeandlinktofromyourGooglePlayStorepage.

ListingDetailsTheGooglePlayStoreasksfortextualinformationaboutyourapplicationtodisplaytoshoppers,includingthetitle,shortdescription(formerlycalledpromotionaltext),andfulldescription.

There’saShortDescriptionfieldthathasonly80characters,andit’smandatory.WhenyourappisshownatthetopofalistinGooglePlayStore,it’sthePromoGraphicandtheShortDescriptionthatgetdisplayed.

Thefulldescriptionisalsomandatory,anditallowsupto4,000characters.IfyouhavewrittenaseparateEULAforyourusers,providealinktoitinyourfulldescriptiontextsoshopperscanviewitpriortodownloadingyourapplication.Considerthatshopperswill

likelyusesearchtolocateapplications,sobesuretoputappropriatewordsintoyourtexttomaximizeyourhitrateonsearchesrelatedtoyourapplication’sfunctionality.It’sworthwhiletoputashortcommentinthetextthatsaystoe-mailyouiftheuserrunsintoproblems.Withoutthissimpleprompt,peoplearemorelikelytoleaveanegativecomment,andanegativecommentreallylimitsyourabilitytotroubleshootandsolvetheproblem,ascomparedtoane-mailexchangewiththeaffecteduser.

Onedrawbacktotheusercommentsmechanismdescribedearlieristhatitdoesnotdistinguishtousersthespecificversionofyourapplication.Ifnegativereviewsarereceivedagainstversion1,andyoureleaseversion2witheverythingfixed,thereviewsfromversion1arestillthere,andshoppersmaynotrealizethatthosecommentsdon’tapplytothenewversion.Whenreleasinganewversionofanapplication,theapplicationrating(numberofstars)doesnotgetreset,either.Partlyforthisreason,GooglestartedprovidingaRecentChangestextfieldwhereyoucandescribewhat’snewinthisrelease.Thisiswhereyoucouldindicatethatacertainproblemhasbeenfixedortellwhatthenewfeaturesare.ThePlayStorealsoprovidestheabilitytoseejustthereviews/commentsforthelatestversion,butbydefaultthereviewsandcommentsareshownforallversions.

Oneofyourresponsibilitieswhenwritingthetextforyourapplicationistodisclosethepermissionsthatarerequired.Thesearethesamepermissionsassetinthe<uses-permission>tagsofyourAndroidManifest.xmlfilewithinyourapplication.Whentheuserdownloadsyourapplicationtotheirdevice,AndroidwillchecktheAndroidManifest.xmlfileandasktheuseraboutalloftheuses-permissionrequirementsbeforecompletingtheinstall.Soyoumightaswelldisclosethisupfront.Otherwise,yourisknegativereviewsfromuserssurprisedthatanapplicationrequiressomepermissionthattheyarenotpreparedtogrant,nottomentiontherefunds,whichalsocountagainstyourDeveloperCompositeScore.Similartopermissions,ifyourapplicationrequiresacertaintypeofscreen,acamera,orotherdevicefeature,thisshouldbedisclosedinyourtextdescriptionsofyourapplication.Asabestpractice,youshoulddisclosenotonlywhatpermissionsandfeaturesyourapplicationneeds,butalsowhatyourapplicationwilldowiththem.Youshouldanswertheuser’squestioninadvance:whydoesthisapprequireX?

Whenuploadingyourapplication,youwillneedtochooseanapplicationtypeandacategory.Asthesevalueschangewithtime,wewon’tlistthemhere,butit’seasytogototheAddnewapplicationscreentoseewhattheyare.

PublishingOptionsYoumustchoosetwocontentratings.Theideaistogiveconsumersanideaoftheappropriatenessofanapplicationforcertainagegroups.Thescaleforthefirst(older)contentratingincludesHigh,Medium,andLowMaturity,andEveryone.Choosingtherightleveldependsonthecontentinyourapplicationandhowmuchofthatcontentthereis.Googlehasrulesaboutlocation-awarenessandpostingorpublishinglocations.It’sbesttoreadtherulesforyourselfhere:https://support.google.com/googleplay/android-developer/answer/188189.Thesecondcontentratingisderivedafteryou

completeaquestionnaire.Youwillactuallygetseveralcontentratings,bycountry,dependingonhowyouanswerthequestionnaire.Thequestionnairetakessomeofthesubjectivityoutofthecontentrating.

Nextyousetthepriceofyourapplication.BydefaultthepriceisFree,andyoumusthavepreviouslysetupaMerchantAccountinGoogleCheckoutifyouwanttochargeforyourapplication.Settingtherightpriceforanapplicationistricky,unlessyou’vegotsomesophisticatedmarketresearchcapabilities,andeventhenit’sstilltricky.Pricessettoohighcouldturnpeopleoff,andyourisktheeffectsofrefundsifpeopledon’tfeelthepricewasworthit.Pricessettoolowcouldalsoturnpeopleoffbecausetheymightthinkit’sacheapapplication.

Oneofthelastdecisionstomakebeforeuploadingyourapplicationistochoosethelocationsandcarriersforyourapplicationtobevisibleto.BychoosingAll,yourapplicationwillbeavailableeverywhere.However,youmaywanttorestrictdistributiongeographicallyorbycarrier.Dependingonwhatfunctionalityisinyourapplication,youmayneedtorestrictbylocationinordertocomplywithUSexportlaw.Youmaychoosetorestrictyourapplicationbycarrierifyourapplicationhascompatibilityissueswithcertaincarriers’devicesorpolicies.Toseecarriers,clicktheShowoptionslinknexttothecountry,andtheavailablecarriersforthatcountrywillbedisplayed,allowingyoutochoosetheonesyouwant.ChoosingallalsomeansthatanynewlocationsorcarriersthatGoogleaddswillautomaticallyseeyourapplicationwithnointerventionfromyou.

Inadditiontocountryandcarrierchoices,GooglePlayStorealsoallowsyoutorestrictyourapplicationtocertaindevices.Bydefault,thedeviceslistisfilteredbasedonyourmanifestfile,inwhichyou’vespecifiedthefeaturesandsoonthatyourapplicationrequires.ThissectionoftheUploadscreenallowsyoutofurtherrestrictotherdevices.Youwouldprobablyonlywanttodothisiftherewasaknownissuewithaparticulardevicesuchthatyouwereunabletogetyourapplicationtoworkonthatdeviceeventhoughitoughtto.

AndroidalsoofferstheoptiontouploadmultipleAPKsforthesameapplication.ItenablesyoutohaveasingleentryonGooglePlayStorebuttohaveseparatebuildforphonesandtablets.Seehttp://android-developers.blogspot.com/2011/07/multiple-apk-support-in-android-market.htmlandhttp://developer.android.com/google/play/publishing/multiple-apks.html.

ContactInformationEventhoughyourdeveloperprofilecontainsyourcontactinformation,youcansetdifferentinformationwhenuploadingeachapplication.ThePlayStoreasksforawebsite,e-mailaddress,andphonenumberascontactinformationrelatedtothisapplication.Youmustsupplyatleastoneofthesesobuyerscangetsupport,butyoudon’tneedtosupplyallthree.Itisagoodideatonotuseyourpersonale-mailaddresshere,justasyouprobablywouldn’treallywanttogiveoutyourpersonalphonenumber.Whenyou’vemademillionsofdollarsfromsellingyourapplication,you’llwanttoletsomeoneelse

receiveanddealwiththee-mailsfromusers.Bysettingupanapplication-supporttypeofe-mailaddressinadvance,youcaneasilyseparatethesupporte-mailsfromyourpersonale-mails.Ofcourse,youcanalwayschangethesevalueslaterifyouneed/wantto.

ConsentWithallthesedecisionsmade,youmustthenattestthatyourapplicationabidesbyAndroid’sContentGuidelines(basicallynonastystuff)andmakeasecondattestationthatthesoftwareisOKforexportfromtheUnitedStates.USexportlawsapplybecauseGoogle’sserversarelocatedinsidetheUnitedStates,evenifyouareoutsideoftheUnitedStates,andevenifbothyouandyourcustomerareoutsideoftheUnitedStates.Rememberthatyoucanalwayschoosetodistributeyourapplicationthroughotherchannels.Whenallyourinformationisinandyourgraphicsareuploaded,goaheadandclicktheSavebutton.Thiswillprepareeverythingforyourapplicationtobereadytogolive.

YoucanthenpublishyourapplicationbyclickingthePublishbutton.GooglePlayStorewillperformsomechecksonyourapplication—forinstance,checkingyourapplication’scertificatefortheexpirationdate.Ifallgoeswell,yourapplicationwillsoonbeavailablefordownload.Congratulations!

UserExperienceonGooglePlayStoreThePlayStoreapphasbeenavailableondevicesforsometimenow,anditisavailableovertheInternet.Developersdon’thaveanycontroloverhowGooglePlayStoreworks,otherthantoprovidegoodtextandgraphicsfortheirapplication’slistinginthePlayStore.Therefore,theuserexperienceisprettymuchuptoGoogle.Fromadevice,ausercansearchbykeyword;lookattopdownloadedapplications(bothfreeandpaid),featuredapplications,ornewapplications;orbrowsebycategories.Oncetheyfindanapplicationtheywant,theysimplyselectit,whichpopsupanitemdetailsscreenallowingthemtoinstallitorbuyit.BuyingwilltaketheusertoGoogleCheckouttoconductthefinancialpartofthetransaction.Oncedownloaded,thenewapplicationshowsupwithalltheotherapplications.

FromtheInternetwebsiteforGooglePlayStore(https://play.google.com),theuserinterfacelooksaboutthesame,albeitmuchlargerthanmostdevicescreens.Onedifferenceisthattheweb-basedGooglePlayStoreexpectstheusertologintotheirGoogleaccounttousethePlayStore.ThisallowsGoogletoconnectyourwebexperienceonGooglePlayStoretoyouractualdevice.Thismeanstwothings:whenusingthewebsite,GooglePlayStoreknowswhatapplicationsarealreadyinstalledonyourdevice;andwhenyoumakeapurchaseontheGooglePlayStorewebsite,thedownloadcanbesenttoyourdevice(ordevices)andnottowhatevercomputeryouhappentobebrowsingon.

GooglePlayStorehasanoptiontoviewdownloadedapplicationsinMyApps.Thisareacontainsallinstalledappsandanyappsthatyou’vepurchased,evenifyou’veremovedthem(perhapsyouremovedthemjusttomakeroomforotherapplications).Thismeansyoucoulddeleteapaidappfromyourphoneandthenreinstallitlaterwithouthavingto

repurchaseit.Ofcourse,ifyouoptedforarefund,theappwillnotshowupinMyApps.

ThelistofappsinMyAppsistiedtoyourGoogleAccountusedacrossallyourdevices.Thismeansyoucouldswitchtoanewphysicaldeviceandstillhaveaccesstoalltheappsyou’vepaidfor.Butbeware.SinceyoumighthavemultipleidentitieswithGoogle,youmustusetheexactsameidentityasbeforetogetyourappsonanewdevice.WhenviewingappsinMyApps,anythathaveupgradesavailablewillindicatethisandallowyoutogettheupgrade.

GooglePlayStorefiltersapplicationsavailabletousers.Itdoesthisinanumberofways.UsersinsomecountriescanonlyseefreeapplicationsbecauseofthecommercelegalitiesinvolvedforGoogleinthatcountry.Googleistryinghardtoovercomecommercehurdlessoallpaidappswillbeavailableeverywhere.Untilthattimecomes,usersinsomecountrieswillbeunabletoaccesspaidapps.UserswithdevicesrunningolderversionsofAndroidwillnotbeabletoseeapplicationsthatrequireanewerversionoftheAndroidSDK.Userswithdeviceconfigurationsthatarenotcompatiblewiththerequirementsoftheapplication(expressedvia<uses-feature>tagsintheAndroidManifest.xmlfile)willnotbeabletoseethoseapplications.Forexample,applicationsnotspecificallysupportingsmallscreenscannotbeseeninGooglePlayStorebyusersondeviceswithsmallscreens.Thisfilteringismostlyintendedtoprotectusersfromdownloadingapplicationsthatwillnotworkontheirdevice.

IfyouarepurchasingappsinGooglePlayStorefromothercountries,yourtransactionmaybesubjecttocurrencyconversion,whichcanalsocarryanadditionalfee,unlessthesellerhasspecifiedpricinginyourlocalcurrency.You’rereallypurchasingusingtheGoogleCheckoutfromtheseller’scountry.GooglePlayStorewilldisplayanapproximateamount,buttheactualchargescouldvarydependingonwhenthetransactionisplacedandwithwhichpaymentprocessor.Buyersmaynoticeapendingtransactionagainsttheiraccountforasmallamount(forexample,US$1).ThisisdonebyGoogletoensurethatthepaymentinformationprovidediscorrect,andthispendingchargewillnotactuallygothrough.

AfewwebsitesareavailablethatmirrortheGooglePlayStoreapplistings.Shopperscansearch,browsecategories,andfindoutaboutGooglePlayStoreapplicationsovertheInternetwithouthavingadevice.ThisgetsaroundthefilteringthatGooglePlayStoredoesbasedonyourdeviceconfigurationandlocation.However,thisdoesnotgetappsontoyourdevice.Examplesofthesemirrorsitesarewww.androlib.com,andwww.appszoom.com.

BeyondGooglePlayStoreGoogle’sPlayStoreisnottheonlygameintown.YouarenotforcedintousingGooglePlayStoreatall.Youshouldconsiderutilizingotherchannelsofdistribution,notonlytomakeyourappavailabletomorepeopleinmorecountries,butalsototakeadvantageofotherpaymentprocessorsandopportunitiestomakemoney.

ThereareAndroidappstorescompletelyseparatefromGooglePlayStore,thebiggestofwhichisprobablyAmazon.OtherexamplesofAndroidappstoresare

http://mall.soc.io/apps,http://slideme.org,www.getjar.com,andhttps://f-droid.org/.Fromthesesites,youcansearch,browse,findoutaboutapps,andalsodownloadapps,eitherfromadeviceorviaawebbrowser.Thesesitesdon’thavetoabidebyGoogle’srules,includingthetransactionfeesforpaidappsandmethodsofpayment.PayPalandotherpaymentprocessorscanbeusedtopurchaseappsontheseseparatesites.Thesesitesalsodon’tnecessarilyrestrictbylocationordeviceconfiguration.SomeofthemprovideanAndroidclientthatcanbeinstalled,orinsomecasesmaycomepreinstalledonadevice.Userscansimplylaunchabrowserontheirdeviceandfindtheapptheywanttodownloadviathewebsite;whenthefileissavedtothedevice,Androidknowswhattodowithit.Thatistosay,adownloaded.apkfileistreatedasanAndroidapplication.IfyouclickitintheDownloadhistoryofthebrowser(nottobeconfusedwithMyApps,coveredearlier),youwillbepromptedtoseeifyouwanttoinstallitornot.ThisfreedommeansyoucansetupyourownmethodsofdownloadingAndroidapplicationstousers,evenfromyourownwebsiteandwithyourownpaymentmethods.Youmuststilldealthoughwithcollectinganynecessarysalestaxandremittingittotheappropriateauthorities.

WhilenotrestrictedbyGoogle’srules,thesealternatemethodsofappdistributionmaynotofferthesamesortofbuyerprotectionsthatarefoundinGooglePlayStore.Itmaybepossibletopurchaseanapplicationthroughanalternatemarketthatwillnotworkonthebuyer’sdevice.Buyersmaybeatgreaterriskofmalwareonthealternatemarkets.Thebuyermayalsoberesponsibleforcreatingbackups,incasetheylosetheapplicationfromtheirdevice,orfortransferringapplicationsiftheyswitchtoanewdevice.

Theseothermarketsallowyoutomakemoneyonthesaleofeachapp.You’vealsogottheabilitywithintheseothermarketstoimplementalternatepaymentmechanisms,ortoimplementadsandmakemoneythatway.

RememberthatGoogledoesnotrestrictdevelopersfromsellingtheirapplicationsinmultiplemarketsatthesametimetheysellthroughGooglePlayStore.Soconsiderallyouroptionstomakethemostofyourefforts.

ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:

http://developer.android.com/guide/topics/manifest/manifest-intro.html:TheDeveloperGuidepagetotheAndroidManifest.xmlfile,withdescriptionsofhowtousethesupports-screens,uses-configuration,anduses-featuretags.

http://developer.android.com/guide/practices/screens_support.htmlTheDeveloperGuidepage“SupportingMultipleScreens,”whichcontainslotsofgoodinformationondealingwithdifferentscreensizesanddensities.

http://developer.android.com/design/style/iconography.htmlTheDesignGuidepage“Iconography,”whichcontainslotsofgoodinformationondesigningeffectiveiconsforyourapplication.

http://android-developers.blogspot.com/2010/09/securing-android-lvl-applications.htmlandhttp://android-developers.blogspot.com/2010/09/proguard-android-and-licensing-server.html:BlogpostsonhowtousetheLicenseVerificationLibrary(LVL)inwaysthatpreventpiracy.

http://proguard.sourceforge.net/:ThemainsiteforProGuard,whichincludesdocumentation.

SummaryYouarenowequippedtotakeontheworldwithyourAndroidapplications!Hereisarundownofthetopicswecoveredinthischapter:

HowtogetestablishedasaGooglePlayStorePublisher(thatis,Developer)soyoucanpublishtoGooglePlayStore.

TherulesaslaidoutintheGooglePlayDeveloperDistributionAgreement.

GivingGoogleitsshareofyourrevenueifyouaresellingthroughGooglePlayStore.WealsodiscussedhowGoogledoesnotwanttoseecompetitionfromwithinthePlayStore.

Yourresponsibilityforpayingtaxesonrevenuesfromyourapplications.

TheGooglePlayStorerefundpolicy,boththepublishedandtherealone.

Howuserscangetcopiesofyourapplicationanytimeinthefutureaslongastheypaidforitonce.

TheAndroidbrandingrules.Makesureyoudon’tviolateanycopyrightassociatedwithAndroid,images,orfonts.

TheDeveloperConsoleanditsfeatures.TheDeveloperConsolecollectsuserfeedbackanderrorreportsfromusers.

Preparingyourapplicationforproduction,includingtesting,LVLandProGuardtofightpiracy,andusingresourcevariationsandtagsinAndroidManifest.xmltofilterwhichdevicesyourapplicationwillbeavailableto.

Adviceregardinglocalizingyourapplicationbylanguageand/orculture.

TheGooglePlayStoreuserinterface,bothondeviceandontheInternet/Web.

ThefactthatGooglePlayStoreisnottheonlygameintown,andthatyoucansellyourapplicationinotherplacesontheInternet,allatthesametime.

IndexA

Accelerometers

coordinatesystem

deviceangle

displayorientation

gravity

landscapemode

magneticfieldsensor

AccountsFunctionTester

acos()method

Actionbar

definition

list-basedactionbar

searchviewwidget

manifestfile

menuitem

searchableXMLfile

searchresultsactivity

searchtarget

standardactionbar

tabbedactionbar

ACTION_DOWNevent

ACTION_DRAG_ENDED

ACTION_DRAG_ENTERED

ACTION_DRAG_EXITED

ACTION_DRAG_LOCATION

ACTION_DRAG_STARTED

ACTION_DROP

ACTION_MOVEevent

ACTION_UPevent

Activity.getResources()

Activity.managedQuery()method

Activity.onCreate()

Adapters

ArrayAdapter

createFromResource()method

notifyDataSetChanged()method

classhierarchy

GalleryControl

GridViewcontrol

definition

JavaImplementation

ListAdapter

setContentView()

ListViewcontrol

AcceptUserInput

additionalbutton

codeimplementation

doClick()method

getCheckItemIds()method

getItemAtPosition()method

LinearLayout

ListActivity

makeText()method

notifyDataSetChanged()method

onItemClick()method

setListAdapter()method

simple_list_rowlayout

UIdefinition

SimpleCursorAdapter

SpinnerControl

addProfileContact()

AggregatedContact()

Aggregatedcontacts

cursorfor

URI-basedcursor

definition

listContacts()

LookupURI-BasedCursor

Alarmmanager

broadcastreceiver

Calendarobject

IntentPointing

PendingIntentclass

persistence

repeatingalarm

RTCtime

setExact()method

set()method

TestReceiver

am.getAccounts()

@android\:id/empty

@android\:id/listview

Androidactivitylifecycle

activitycallbacks

ObjectonRetainNonConfigurationInstance()

voidonCreate

voidonDestroy()

voidonPause()

voidonRestart()

voidonRestoreInstanceState

voidonResume()

voidonSaveInstanceState

voidonStart()

voidonStop()

Androidappstores

Androidarchitecture

calculatorapplication

ACTION_GET_CONTENT

activitylifecycle.Androidactivitylifecycle

directorystructure

implicit/explicit

intentactivities

intent/activities

intents

ProgrammingLogic(seeProgrammingLogic)

realdevice

savingstate

UI.UserInterface(UI)

finesseapps

integration

robustapplications

UIessentials

Androidcustomadapter

ManateeAdapter

GridView

javaimplementation

XMLlayout

AndroidDevelopmentTools(ADT)

Androideclipseenvironment

AndroidSDK

eclipse4.2

installingADT

JDK6

PATH

pointingADT

toolswindow

AndroidInterfaceDefinitionLanguage(AIDL)

definition

IStockQuoteServiceinterface

bindService()method

Compiler-GeneratedJavaFile

implementation

MainActivity.javafile

manifestdeclaration

ServiceConnectioninterface

StockQuoteClient

unbindService()method

Messenger/Handler

clientactivitycode

clientfragmentcode

servicecode

nonprimitivetypes

Parcelableinterface

AndroidManifest.xmlfile

implementation

IStockQuoteService.aidl

MainActivity.java

main.xml

Person.aidlfile

StockQuoteService2implementation

StockQuoteService2layout

Androidlayoutmanagers

definition

FrameLayout

GridLayout

LinearLayout

gravity

gravityvs.layout_gravity

horizontalconfiguration

textfields

weight

weightconfigurations

RelativeLayout

TableLayout

AndroidManifest.xmlfile

Androidresources

applicationclass

applicationcontext

arbitraryXMLfile

assetsdirectory

directorystructure

drawableXMLresourcefile

Javacode

qualifiers

rawresourcefile

AndroidRunTime(ART)

Androidsecuritymodel

digitalcertificate

keypair

keystore

runtimesecurity

Androidmanifesteditortool

featuresandresources

processboundary

requiredpermission

URIpermissions

signingapplications

adbtool

debug.keystorefile

exportwizard

installingupdates

jarsignertool

self-signedcertificate

VeriSign

zipaligntool

Androidservice

AIDL(seeAndroidInterfaceDefinitionLanguage)

AsyncTask

local

AndroidManifest.xmlfile

BackgroundService.java

bindService()

Context.startService()

definition

displayNotificationMessage()method

drawablefile

e-mailapplication

IntentService

interrupt()method

MainActivity

main.xmllayoutfile

onBind()method

onCreate()method

onDestroy()method

onStartCommand()method

ServiceWorkerclass

startIdparameter

startService()

stop*()methods

ThreadGroupclass

remote

bindService()

definition

languagetranslationapplication

RPCmechanism

Android’sfundamentalcomponents

activity

AndroidManifest.xml

AVD

contentprovider

fragment

intent

service

view

Androidsoftwaredevelopmentkit(SDK)

JDK

packages

savingstate

contentproviders

externalfiles

internalfiles

networkstorage

O/Rmappinglibraries

sharedpreferences

SQLite

tools

Androidstudio

definition

homescreen

Javainstallation

Androidstyles

definition

EditText

parentstyle

TextView

Androidthemes

Androidvirtualdevice(AVD)

animate()method

Animation

APIresource

types

AnimationBuilder

AnimatiorSetBuilder

AnimatorSet

apkfile

ApplicationNotResponding(ANR)

Applicationprogramminginterface(API)

AppWidgetProviderInfoclass

argsBundleargument

AsyncTask

activitypointer

devicerotation

implementation

concretetypes

definition

doInBackground()method

execute()method

onPostExecute()callbackmethod

onPreExecute()method

parameters

progressdialog

publishProgress()method

sourcecode

threadpools

manageddialogs

pseudocode

retainedobjectsandfragmentdialogs

activitylifecycle

AsyncTesterFragmentobject

attach()method

fragmentapproach

keycodesnippets

layoutfile

onCreateDialog()method

onDestroy()method

onRetainNonConfigurationInstance()method

onStart()method

pseudocode

releaseResources()method

retainedheadlessfragment

rootRADO

sampling

setProgress()method

AsyncTaskLoader

autoPause()method

BBackgroundService’sonDestroy()method

BaseAdapter

BDayWidgetModelclass

bindService()method

BirthdayWidget

Bluedot

BooleanButtonclass

Broadcastreceiver

in-processreceivers

long-runningservices

abstractclass

ALongRunningReceiver

codeexecution

getLRSClass()method

handleBroadcastIntent()method

IntentService

LightedGreenRoomabstraction

nonstickyservice

onCreate()method

onDestroy()method

onStartCommand()method

protocols

redelivermode

stickyservice

Test60SecBCR

Test60SecBCRService

manifestfile

notificationmanager

monitoring

sendingnotification

startActivity()method

out-of-processreceivers

sendBroadcast()method

sendOrderBroadcastmethod

TestReceiver2

Broadcastreceiversamplecode

CCalendar.getInstance()

c.close()

c.getColumnCount()

c.getCount()

Chronometer

clearCheck()method

Clientapplication

ClipData

c.moveToFirst()

cnamesBuffer.toString()

Compatibilitylibrary

APIs

retrofitting

tablets

v4supportlibrary

v7supportlibrary

v8supportlibrary

v13supportlibrary

Complementaryfilters

Configurationchanges

configurationfactors

destroy/createcycle

FragmentManager

fragments

onCreate()callback

onRestoreInstanceState()callback

onSaveInstanceState()callback

putInt()

putParcelable()

putString()

saveFragmentInstanceState()

setInitialSavedState()

setRetainInstance()

features

getLastNonConfigurationInstance()

onConfigurationChanged()callback

onRetainNonConfigurationInstance()

UIelements

Consumingservices

Android(seeAndroidservice)

HTTP(seeHttpClient)

ContactData()

contact_entities_viewdatabaseview

ContactsAPI

accounts

AccountsFunctionTester

aggregatedcontacts

cursorfor

URI-basedcursorfor

contact_entities_viewdatabaseview

ContactProviderUI

ContentProviderOperation

backreferences

committingviayielding

containerfor

definition

optimisticlocking

controllingaggregation

detailsadding

groupfeatures

personalprofile

contactdata

dataadding

profile-basedURIs

rawcontacts

reading/writing

photofeatures

rawcontacts(seeRawcontacts)

syncadapters

Contentproviders

Androidresources

bookdatabase

BookProvidercontentprovider

deletemethod

implementation

insertmethod

MIME-typecontracts

projectionmaps

querymethod

register

structureof

updatemethod

UriMatcherclass

ContentValues

ContentValues()

CONTEXT_INCLUDE_CODEflag

Contextmenu

onContextItemSelected()

Populating

TextView

Context.NOTIFICATION_SERVICE

CONTEXT_RESTRICTEDflag

createPackageContext()API

createScaledBitmap()method

cursor

foraggregatedcontacts

URI-basedcursor

CursorLoader

DDalvikDebugMonitorService(DDMS)

databaseviews

contact_entities_view

dataRecord.toString()

Datatable

describeEvent()method

DetailsFragment.java

DeveloperConsole

DialogFragment

AlertDialogFragment

communication

construction

newInstance()method

dismiss()method

implications

onCancel()callback

onDismiss()callback

embeddeddialogs

HelpDialogFragment

MainActivity

MyDialogFragment

onCreateDialog()

onCreateView()

OnDialogDoneListener

PromptDialogFragment

show()method

Dialogs

Android

dialogfragments(seeDialogFragment)

toast

Digitalcertificate

DirectAccessBookDBHelper

Display.getRotation()

doInBackground()method

Dot.java

doWhenMapIsReady()method

Drag-and-dropimplemention

ACTION_UPevent

Android3.0

ACTION_DRAG_ENDED

ACTION_DRAG_ENTERED

ACTION_DRAG_EXITED

ACTION_DRAG_LOCATION

ACTION_DRAG_STARTED

ACTION_DROP

DragEventobject

ClipData

customview

Dot

Dot.java

DragShadowBuilder

draw()method

DropZone.java

dropzone.xml

FrameLayout

invalidate()method

layout_height

layout_width

layoutXML

LinearLayout

main.xmlfile

ObjectAnimatorclass

onCreateView()method

onDrag()

onMeasure()method

palette.xml

startDrag()

TextView

TouchDragDemo

userinterface

Viewobject

DragEvent.getResult()method

DragShadowBuilder

draw()method

DropTarget

DropZone.java

dropzone.xml

Dynamicmenus

EEndUserLicenseAgreement(EULA)

execute()method

executeOnExecutor()method

Expandedmenu

FfalseBtnTop

FILL_PARENTvs.MATCH_PARENT

findViewById()

for(c.moveToFirst()

Fragments

definition

FragmentManager

enableDebugLogging()method

getFragment()method

MainActivity

onCreateView()method

persistence

portraitmode

putFragment()method

referencingfragments

saveFragmentInstanceState()method

setContentView()

setRetainInstance()

showDetails()method

TitlesFragmentclass

FragmentTransactions

findFragmentById()method

FrameLayout

setCustomAnimations()method

setTransition()method

showDetails()method

ViewGroupclass

lifecycle

newInstance()method

onActivityCreated()callback

onAttach()callback

onCreate()callback

onCreateView()callback

onDestroy()callback

onDestroyView()callback

onDetach()callback

onInflate()callback

onPause()callback

onResume()callback

onSaveInstanceState()callback

onStart()callback

onStop()callback

onViewCreated()callback

onViewStateRestored()callback

setRetainInstance()

StaticFactoryMethod

setTargetFragment()

startActivity()

structure

tabletandsmartphoneUI

tabletUI

XMLlayout

DetailsFragmentclass

FrameLayout

getShownIndex()method

landscapemode

MainActivity

newInstance()method

onCreateView()method

Shakespeareclass

Frame-by-frameanimation

addFrame()method

AnimationDrawableclass

setOneShot(true)method

sourcecode

GgatherControls()

GCMConnectionServers

Geocoding

definition

latitudeandlongitude

mapfragment

methods

Geofencing

addProximityAlert()method

GeofencingApiDemo

geoMagField.getDeclination()

Gestures

GestureDetectorandOnGestureListeners

pinchgesture

getActionBar()

getAction()method

getContacts()

getCount()method

getEdgeFlags()method

getFirstContact()

getFragmentManager()method

getFragment()method

getFromLocationName()method

getHttpClient()method

getItemId()method

getItem()method

getItemViewType()method

getLastNonConfigurationInstance()

getMinDelay()

getOrientation()

getPointerCount()

getProgressBar()method

getQuaternionFromVector()

getResult()

getRotationMatrix()

getSearchableInfo

getSupportFragmentManager()

getView()method

getViewTypeCount()method

getXVelocity()method

getYVelocity()method

GoogleCloudMessaging(GCM)

authenticatingGCMcommunication

buildingAndroidapplication

configureprojectdependencies

gcm.send()method

manifestproperties

messageID.incrementAndGet()method

onCreate()method

registerAppInBackground()

registering

sendMessage()method

third-partyservice

clientapplication

componentsandmessageflow

definition

GCMAPI

GCMConnectionServers

GoogleDeveloperConsole

remoteapplicationserver

GooglePlayDeveloperDistributionAgreement(GPDDA)

GooglePlayServices

GooglePlayStore

AndroidManifest.xmlfile

apkfile

applicationicon

developerconsole

GPDDArules

licensingservice

localizing

ProGuard

publisher

screensizes

testing

uploading

consent

contactinformation

graphics

listingdetails

publishingoptions

URIscheme

userexperience

Gravitysensors

Gyroscopesensor

HHandlers

AsyncTaskclass

DeferWorkHandlersourcecode

definition

doDeferredWork()method

handleMessage

messageobject

obtainMessage()method

respondToMenuItem()call

setData()method

sleepmethod

workerthread

HelloAndroidApp

artifacts

debuggingtools

launchoptions

lifecycle

realdevice

structure

hideProgressbar()

Homescreenwidgets

AppWidgetProviderclass

BDayWidgetProviderclass

collections

configurationactivity

BDayWidgetModelobject

BirthdayWidget

ConfigureBDayWidgetActivity

layoutdefinition

tasks

configureattribute

definition

filesimplementation

initialLayoutattribute

onClickarea

onDeleted()method

onDisabled()method

onEnabled()method

onUpdate()method

previewImageattribute

resizemodeattribute

uninstallingpackages

userexperience

viewmouseclickeventcallbacks

widgetinstancedeletion

widgetprovider

BDayWidgetModel

boundaryboxshape

ConfigureBDayWidgetActivityactivity

implementation

layoutfile

XMLfile

howRawContactsDataForRawContact()

HttpClient

AndroidHttpClient

Apacheversion

HTTPGETrequest

ANR

HttpGetDemo.java

parameters

HTTPPOSTrequest

execute()method

HTTPPOSTcall

MultipartPOSTCall

NameValuePairobject

setEntity()method

UrlEncodedFormEntityobject

HttpURLConnection

JSON

multithreadingissues

BasicResponseHandler

getHttpClient()method

ThreadSafeClientConnManager

protocolexceptions

SOAPwebservice

timeouts

transportexceptions

XMLPullParser

IIconmenu

inflate()method

insertName()method

insertPhone()method

insertProfileRawContact()

insertRawContact()

Interpolators

interrupt()method

invalidate()method

invokePick()

isEnabled()method

J,KJavafile

JavaScriptObjectNotation(JSON)

JavaSEDevelopmentKit(JDK)

LLAUNCHER

Layoutanimation

AccelerateInterpolator

ActivityCode

alpha.xmlfile

BounceInterpolator

interpolators

list_layout.xmlfile

ListView

ListViewXMLfile

rotateXMLfile

scaleanimationXMLfile

translateandalphaanimationsfile

types

XMLfile

LicenseVerificationLibrary(LVL)

Lightsensor

Linearaccelerationsensor

listContacts()

listLookupUriColumns()

ListPreference

entryValuesarray

flight_sort_options_valuesarray

userinterface

XML

loaderCallbacks

LoaderManager

LoaderManager.destroyLoader(loaderid)

LoaderManager.initLoader()

LoaderManager.LoaderCallbacks

LoaderManager.restartLoader()

Loaders

activity/fragment

activityloadingdata

Activity.onCreate()

APIclasses

argsBundleargument

AsyncTaskLoader

characteristics

cursor

CursorLoader

developer-assigneduniquenumber

ListActivity

ListActivityLayout

loaddata

loaderCallbacks

LoaderManagerCallbacks

LoaderManagerobject

onCreateLoader()method

onCreate()method

onCreateOptionsMenu()method

onLoaderReset()method

onLoadFinished()method

resources

stringresources

Location-basedservices

AVD

DDMS

emulatorconsole

FusedLocationProviderApi

geocoding

definition

latitudeandlongitude

mapfragment

methods

geofencing

addProximityAlert()method

GeofencingApiDemo

getLastLocation()method

GoogleDirections

GooglePlayServices

GoogleClientApiclient

intentslaunching

isGooglePlayServicesAvailable()method

isUserRecoverableError()method

onConnectionFailed()callback

showErrorDialogFragment()method

tryToConnect()method

hasAccuracy()method

locationproviders

locationupdates

MapFragment

coding

FragmentActivity

FrameLayout

locationupdation

mapdisplay

maptiles

maptypes

MyLocation

panmaps

trafficlayer

WhereAmI

zoom

MapsAPIkey

Google

manifestfile

markers

CameraUpdateFactoryclass

LatLngBoundsobject

MarkerOptionsfeatures

MyMapFragment.java

onConnected()callback

requestLocationUpdates()method

Settings.Secureclass

staticmethod

WhereAmILocationAPI

LogCat

Long-runningreceiversandservices

abstractclass

ALongRunningReceiver

codeexecution

getLRSClass()method

handleBroadcastIntent()method

IntentService

LightedGreenRoomabstraction

nonstickyservice

onCreate()method

onDestroy()method

onStartCommand()method

protocols

redelivermode

stickyservice

Test60SecBCR

Test60SecBCRService

Low-passfilter

MMagneticfieldsensor

accelerometers

compass

MainActivity’sstopService()method

Mapping.txtfile

MediaAPIs

android.media.MediaPlayerclass

contentformats

playingaudio

AsyncPlayer

AudioTrack

create()method

getExternalStoragePublicDirectory()method

JetPlayer

killMediaPlayer()method

layoutandcode

onCreate()method

playAudio()method

prepareAsync()method

release()method

setDataSource()method

setLooping()method

setVolume()method

SoundPool

stop()method

userinterface

playingvideo

SDCards

Menu.addSubMenu()

Menugroups

creation

removeGroup()

setGroupCheckable()

setGroupEnabledmethod()

setGroupVisible()

MenuInflater

MenuItem

icon

intent

JavaCode

listener

MenuXMLresourcefile

creation

menuitems

populatingactivity

MotionEvents

ACTION_CANCELevent

ACTION_DOWNevent

ACTION_MOVEevent

ACTION_OUTSIDEevent

ACTION_UPevent

LogCatrecords

mainactivity,Javacode

onTouchEvent()method

onTouch()method

recycle()method

ReturnsFalsebutton

setOnTouchListener()method

TouchDemo1application

Javacode,Buttonclasses

sampleLogCatmessages

UI

XMLlayoutfile

VelocityTracker

Multitouch

ACTION_MOVEevent

ACTION_SCROLL

ACTION_UPvalue

getAction()method

LogCatmessages

XMLlayout

MusicalInstrumentDigitalInterface(MIDI)

NnewInstance()method

Non-configurationinstancereference

Non-streamingsensor

OObjectAnimator

ObjectonRetainNonConfigurationInstance()

obtain()

onAccuracyChanged()

onActivityCreated()callback

onAttach()callback

onBind()method

onCheckedChanged()method

onContextItemSelected()

onCreate()

onCreate()callback

onCreateContextMenu()

onCreateLoader()method

onCreate()method

onCreateOptionsMenu()method

onCreateView().callback

onCreateView()method

onDestroy()callback

onDestroy()method

onDestroyView()callback

onDetach()callback

onDrag()

onHandleIntent()call

onLoaderReset()method

onLoadFinished()method

onLocationChanged()method

onMapReady()callback

onMeasure()method

onMenuItemClick()

onOptionsItemSelected()

onPause()

onPause()callback

onPerformSync()method

onPostExecute()callbackmethod

onPreExecute()method

onPrepareOptionsMenu()

onProgressUpdate()method

onQueryTextchange()method

onReceive()method

onRestoreInstanceState()

onResume()callback

onResume()method

onRetainNonConfigurationInstance()

onSaveInstanceState()callback

onScale()

onSensorChanged()method

onStart()callback

onStartCommand()method

onStop()callback

onTouchEvent()method

onTouch()method

onUpdate()method

onViewCreated()callback

onViewStateRestored()callback

O/Rmapping

PPackages

libraryproject

compile-timeconcept

design

hard-codedconstants

propertiesdialog

switchstatement

LinuxuserID

manifestfile

shareduserID

shareresourcesanddata

palette.xml

parseResult()

PendingIntentclass

Pop-upmenus

PreferenceActivity

Preferences

AndroidManifest.xml

application’ssavedpreferences

CheckBoxPreference

dependency

DialogPreference

EditTextPreference

flight_sort_option

getSharedPreferences()method

headers

ListPreference(seeListPreference)

mainsettings

main.xml

MultiSelectListPreference

onCreate()method

OnPreferenceChangeListenerinterface

packagename

PreferenceCategory

PreferenceScreens

setOptionText()method

stringresourcevalue

SwitchPreference

XML

Pressuresensor

Processmodel.SeePackages

ProgrammingLogic

activityclass

AndroidManifest.xml

calculatorbuttons

gatheringcontrols

intentobject

layoutfile

onClick()method

placementfiles

ProgressBar

ProGuard

PropertyanimationAPI

activity

AnimatiorSetBuilder

AnimatorListener

AnimatorSet

AnimatorSetBuilderclass

AnimatorSetclass

class

Keyframes

layouttransitionclass

layouttransitionmethods

ObjectAnimator

PropertyValueHolderclass

PropertyValuesHolder

toggleAnimation(View)method

TypeEvaluatormethod

ValueAnimator

ViewPropertyAnimator

ViewPropertyAnimatorclass

XMLfile

XMLtags

PropertyValuesHolder

Proximitysensor

publishProgress()method

Qquery()function

queueSound()method

RRatingBar

RawContact()

Rawcontacts

advantagesanddisadvantages

aggregatedcontacts

ContactData.java

contact_entities_view

cursor

datatable

definition

RawContact.java

SQLiteDatabase

structureof

view_contacts

rc.toString()

ReceiveTransitionsIntentServicemethod

registerListener()method

Remoteapplicationserver

Remoteprocedurecall(RPC)

RemoteViewsclass

removeGroup()

requestLocationUpdates()method

Rotationvectorsensor

SScaleGestureDetector

ScrollView

searchable.xml

SearchManager.QUERY

sendBroadcast()method

Sensors

accelerometers(seeAccelerometers)

definition

detectionmethods

GeomagneticField

gettingsensordata

gravitysensor

gyroscopesensor

lightsensor

LightSensorMonitorApp

linearaccelerationsensor

magneticdeclination

magneticfieldsensor

pressuresensor

proximitysensor

rotationvectorsensor

SensorListApp

javacode

output

temperaturesensor

types

setContentView()

setCustomAnimations()method

setDataSource()method

setEdgeFlags()method

setEntity()method

setGroupCheckable()

setGroupEnabledmethod()

setGroupVisible()

setIntent(intent)

setMapType()method

setMeasureAllChildren()

setMyLocationEnabled()method

setOnClickListener()method

setOnTouchListener()method

setRepeating()method

setResult()

setRetainInstance()method

setTabListener()

setTrafficEnabled()method

setTransition()method

setupButtons()

showAllRawContacts()

showAllRawProfileContacts()

showDetails()method

showProfileRawContactsData()

showProgressbar()method

showRawContactsCursor()

Sleepmethod

SOAPwebservice

SpinnerAdapterinterface

SQLException

SQLite

databasecreation

databasemigration

DDLs

deletingrows

insertingrows

packagesandclasses

readingrows

sampleSQLcode

SQLitesqlite_masterTable

transactionsmethod

updatingrows

SQLiteCursor

SQLiteDatabase

SQLiteOpenHelper

SQLiteQueryBuilder

startDrag()

Statusbar

stop*()methods

Streamingsensor

StringBuffer()

Styles

Android-providedstyle

attributename

collectionofViewattributes

dynamicstyling

ErrorText.Danger

errorTextView

parentandchildstyle

spannable

staticstyling

SubMenu

SupportMapFragment

System-levelservices

TTabListenerinterface

Temperaturesensor

testAccounts()

Themes

this.getACursor(getRawContactsUri()

this.mContext.getContentResolver()

Threads

Activity.startService

broadcastreceiver

components

TitlesFragment.java

Toast

toString()

TouchDragDemo

Touchscreens

gestures

GestureDetectorandOnGestureListeners

pinchgesture

MotionEventobject

ACTION_CANCELevent

ACTION_DOWNevent

ACTION_MOVEevent

ACTION_OUTSIDEevent

ACTION_UPevent

Javacode,Buttonclasses

LogCatrecords

mainactivity,Javacode

onTouchEvent()method

recycle()method

ReturnsFalsebutton

sampleLogCatmessages

setOnTouchListener()method

UIobjects

VelocityTracker

XMLlayoutfile

multitouch

ACTION_MOVEevent

ACTION_SCROLL

ACTION_UPvalue

getAction()method

LogCatmessages

XMLlayout

Transformationmatrix

trueLayoutTop

Tweeninganimation

TYPE_AMBIENT_TEMPERATURE

TypeEvaluator

UudpateAppWidgetfunction

unbindService()method

URI-basedcursor

foraggregatedcontacts

URLparameter

Userinterfacesandcontrols

buttoncontrols

basicbutton

CheckBox

clickhandler

ImageButton

ImageViews

RadioButton

switch

ToggleButton

dateandtimecontrols

AnalogClock/DigitalClock

DatePicker/TimePicker

MapView

textcontrols

AutoCompleteTextView

EditText

MultiAutoCompleteTextView

TextView

UIdevelopment

android.view.View

android.view.ViewGroup

code

XML

XMLwithcode

UserInterface(UI)layout

autogeneratedIDs

backgroundproperty

calculatorXML

colorresource

customcontrols

EditTextcontrol

file-basedresources

TextViewcontrol

value-basedresources

ViewGroup

widthandheight

XMLcommentspecification

Utils.logThreadSignature()method

VValueAnimator

VelocityTracker

Viewanimation

activity

AnimationListenerclass

Cameraobject

preandposttranslatemethod

rotatemethod

translatemethod

class

initializemethod

interpolatedTime

ListView

Matrixclass

preandposttranslatemethod

PreandPosttranslatepattern

preandposttranslatematrix

setScalemethod

ViewPropertyAnimator

voidonCreate

voidonDestroy()

voidonPause()

voidonRestart()

voidonRestoreInstanceState

voidonResume()

voidonStart()

voidonStop()

WWakefulIntentService

WhereAmI

WhereAmIMarkers

Whitedot

X,Y

XMLPullParser

ZZIPfile

top related